我的github博客 github.com/zhuanyongxi…
ES6之前,JS的函数只能一口气执行到底,Generator的出现使得JS的函数可以做到停顿,根据需要一步步执行。如果没有Generator,可以用闭包来模拟,可这种实现并不方便。
关于generator的运行机制,可以将它简单的总结为三点,详细信息见mdn:
- 是调用生成器函数的时,函数内部代码不会马上执行,它返回的是一个迭代器对象。
- 用返回的迭代器对象调用next方法执行到第一个yield为止,返回yield后面的表达式的值。
- 通过next传入的参数,是作为上一条执行的yield表达式的返回值(所以首次调用next方法时传参无意义)。
对于Generator的应用场景,最合适的应当就是对异步嵌套的优化了。
一段来自《YDKJS》很精彩的代码:
function foo(x,y) {
ajax(
"http://some.url.1/?x=" + x + "&y=" + y,
function(err,data){
if (err) {
it.throw( err );
}
else {
// 关键的地方,在回调函数中再次调用next方法
it.next( data );
}
}
);
}
function *main() {
try {
var text = yield foo( 11, 31 );
console.log( text );
}
catch (err) {
console.error( err );
}
}
var it = main();
// 开始执行,发出请求
it.next();
复制代码
在调用了next方法之后,请求发出,在请求的回调函数里面return
没有意义,拿不到返回值,所以在回调里面再调用一次next方法,并把返回的数据当做参数传入。非常巧妙的运用了Generator的特性。
Generator对异步写法的优化非常优秀,比Promise更好,但promise解决的不光是可读性的问题,还有信任问题。
所以,我们需要用Generator + promise的模式。
基本版:
function foo(x,y) {
return new Promise(function(resolve, reject) {
ajax("http://some.url.1/?x=" + x + "&y=" + y, function(data){
resolve(data);
});
})
}
function *main() {
try {
var text = yield foo( 11, 31 );
console.log( text );
}
catch (err) {
console.error( err );
}
}
var it = main();
var p = it.next().value;
p.then(
function(text){
it.next( text );
},
function(err){
it.throw( err );
}
);
复制代码
这个基础的版本,如果main函数里面有多个步骤,每次调用next方法之后,还要再手写一个Promise链,可以自己封装一个runner方法,一口气把Generator执行到底。
总结一下Generator:它对异步可读性的优化比Promise更好,可有了async/await之后,我也不知道generator还有什么用了(对于模拟多线程之类的应用,我想象不出会有大量应用的场景)。Generator更像是一个过渡性的工具。
async/await
从Generator的思路过来,async/await的方式更像是对Generator的进一步简化。async/await的写法,不需要封装runner也可以执行到底。
function foo(x,y) {
return new Promise(function(resolve, reject) {
ajax("http://some.url.1/?x=" + x + "&y=" + y, function(data){
resolve(data);
});
})
}
async function main() {
try {
var text = await foo( 11, 31 );
console.log( text );
}
catch (err) {
console.error( err );
}
}
main();
复制代码
此外需要注意的是要正确使用await,没有相互依赖关系的异步操作,不要在同一个async函数里面用await,具体参考How to escape async/await hell
参考资料: