使用generator函数改造回调函数
写在前面的话:
generator函数总是与yield一起使用的。
基础知识(ES6)
generator函数的声明:
- 可以使用构造函数 GeneratorFunction 生成
- 使用function* expression 定义
使用第二种更方便。
声明一个generator函数:
function *test() {
// 要执行的代码
}
调用generator函数并不会立即执行函数中的语句,而是返回一个Generator对象。
Generator对象有三个方法:
- Generator.prototype.next() 返回一个由 yield表达式生成的值,值的结构
{value: '值', done: '状态'}
, value的值是yield关键字右侧语句的计算值,done表示generator函数是否最终执行完成。 - Generator.prototype.return() 返回给定的值并结束生成器。
- Generator.prototype.throw() 向生成器抛出一个错误。
如果要执行generator函数中的语句还需要调用next()函数。
调用next()函数后会在遇到的第一个yield语句处暂停,如果要继续执行后面的语句需要继续调用next()函数,如果遇到yield语句会再次暂停,直到所有yield语句执行完毕,返回函数最终的值。
function *test() {
console.log(1);
yield 'qqq';
console.log(2);
yield 'ppp';
return 'ooo';
}
let testGene = test(); // 没有任何打印
console.log(testGene.next()); // 打印1, next()函数返回{value: 'qqq', done: false}
console.log(testGene.next()); // 打印2, next()函数的返回值{value: 'ppp', done: false}
console.log(testGene.next()); // next()的返回值{value: 'ooo', done: true}
注意:yield语句本身并不会返回值(或者说总是返回undefined),真正返回值的是next()函数。
看下面的例子:
function *test() {
let x = yield 'xxx';
console.log(x);
}
let testGene = test();
console.log(testGene.next()); // {value: 'xxx', done: false}
console.log(testGene.next()); //打印x的值为undefined,next()的返回值 {value: undefined, done: true}
对于上面的代码还有要说明的一点,当调用完第一个next()函数后,let x = yield 'xxx';
这条语句仅仅执行了等号右面的部分,等号左面的部分要等到下次调用next()时才执行。这就是前面所说的【遇到yield语句就暂停的意思】。
可以给next()函数传入参数,用来赋值。
function *test() {
let x = yield 'xxx';
console.log(x);
}
let testGene = test();
console.log(testGene.next()); // {value: 'xxx', done: false},在这一步给next()函数传入参数并不会赋值给x。
console.log(testGene.next(123)); //打印x的值为123,next()的返回值 {value: undefined, done: true}
可以稍微花两分钟好好感受一下上面的代码。
有了上面这些基础知识就可以对回调函数进行改造了,如果想了解其他的关于generator函数的基础知识可以点这里MDN。
改造回调函数
假如有两个接口,调用第一个接口成功反馈结果后再调用第二个接口。使用回调函数的写法,需要在调用第一个接口的回调函数里再调第二个接口。
下面使用generator函数来实现同步的顺序调用。
使用知乎的接口:
- https://www.zhihu.com/api/v4/search/top_search/tabs/hot/items
- https://www.zhihu.com/api/v4/hot_recommendation
先写一个ajax请求函数:
let http = require('https');
function requestAjax(url,callback) {
http.get(url, (res) => {
let rawData = '';
res.on('data', (chunk) => { rawData += chunk; });
res.on('end', () => {
try {
const parsedData = JSON.parse(rawData);
console.log(parsedData);
callback(parsedData);
} catch (e) {
console.error(e.message);
callback(e);
}
});
})
}
再写一个generator函数,这个函数的大致结构如下:
function *syncAjax(callback) {
let firstRes = yield requestAjax(url,callback);
console.log('firstRes', firstRes);
if(firstRes.data) {
let secondRes = yield requestAjax(url,callback);
console.log('secondRes', secondRes);
}
return {firstRes,secondRes};
}
我们知道generator函数需要手动调用next()函数才能把函数里的语句执行完,所以我们需要在请求结果的回调里调用next()函数才能让generator函数执行下去。
写一个回调函数
function requestHandler(res) {
geneInstance.next(res); // geneInstance是调用syncAjax()返回的Generator对象
}
为了让requestHandler函数能访问到geneInstance,需要用到闭包,也就是说还需要启动函数:
function run(generatorFnc) {
let geneInstance = generatorFnc(requestHandler);
geneInstance.next();
function requestHandler(res) {
geneInstance.next(res); // geneInstance是调用syncAjax()返回的Generator对象
}
}
代码已经写完了,下面是汇总在一起的:
let http = require('https');
function requestAjax(url,callback) {
http.get(url, (res) => {
let rawData = '';
res.on('data', (chunk) => { rawData += chunk; });
res.on('end', () => {
try {
const parsedData = JSON.parse(rawData);
callback(parsedData);
} catch (e) {
callback(e);
}
});
})
}
function *syncAjax(callback) {
let firstRes = yield requestAjax('https://www.zhihu.com/api/v4/search/top_search/tabs/hot/items',callback);
let secondRes = null;
console.log('firstRes======', firstRes);
if(firstRes.data) {
secondRes = yield requestAjax('https://www.zhihu.com/api/v4/hot_recommendation',callback);
console.log('secondRes======', secondRes);
}
return {firstRes,secondRes};
}
function run(generatorFnc) {
let geneInstance = generatorFnc(requestHandler);
geneInstance.next();
function requestHandler(res) {
geneInstance.next(res); // geneInstance是调用syncAjax()返回的Generator对象
}
}
run(syncAjax);
运行上面的代码,在控制台按顺序打印出了接口数据,是不是很妙?
参考资料
[1] MDN
[2] 深入理解js中的yield
扩展阅读
[1] 如何使用koa2+es6/7打造高质量Restful API
[2] 如何优雅的在 koa 中处理错误