Generator
生成器是es6原生提供的异步编程方案,其语法行为和传统函数完全不同,阮大的《ECMAScript 6 入门》一书中对生成器有比较详尽的介绍,可以参考,还有一些其他的文章比如:
本文主要是通过一些代码示例来记录和总结生成器的用法。
yield 和 next
yield和next在生成器中扮演着非常重要的角色,前者是一个操作符,后者是生成器上的一个函数。
他们具有以下特性:
- 需要调用generator的
next函数,生成器中的语句才开始执行; next函数在生成器之外调用,意味着可以在生成器之外控制生成器内部的操作的执行过程;- 当生成器遇到
yield操作符就立即执行yield之后的语句并暂停,不敢妄言内部原理,姑且感性地比作savepoint; - 当再次调用生成器的
next函数时,生成器从上次发生yield的‘savepoint’继续执行,直到再次遇到yield,或者遇到是return或者throw生成器就退出; next的输入参数在上一次发生yield的地方返回,所以第一次调用next传入的参数没有卵用;next的返回值是一个形如{done:false, value:x}的对象,每次执行next都会使生成器继续执行到下一条yield,next的返回值中的value属性是紧接在这条yield之后的语句执行之后的返回值,如果遇到return或者再也没有yield操作那么返回对象中done=true,value则是return的返回值(没有return时返回undefined);
以上说了很多,先利用next的返回值特性容实现一个无限的斐波那契数列,他永远不会返回done=true
const f = function* fibonacci() {
let [a, b] = [0, 1];
for (;;) {
yield a;
[a, b] = [b, a + b];
}
}();
//执行三次,得到0,1,1
for (let i of Array(3).keys()) {
console.log(f.next());
}
接下来通过一段代码看看next和yield在传值和返回值上的情况,如下:
const iter = function* gen() {
console.log(`yield ${(yield 'a' + 0)}`);
console.log(`yield ${(yield 'b' + 1)}`);
return 'c' + 2;
}();
console.log(`next:${iter.next(0).value}`); //输出 next:a0
console.log(`next:${iter.next(1).value}`); //输出 yield 1 next:b1
console.log(`next:${iter.next(2).value}`); //输出 yield 2 next:c2
- 第一个
next触发生成器执行到第一个yield,并立即执行'a' + 0 = 'a0',a0作为这次next的返回值 - 第二个带入参为1的
next触发生成器执行到第二个yield,此时第一个yield返回1,并执行到下一条yield,立即这条yield后面你的'b' + 1 = 'b1',b1作为这次next的返回 - 第三个next执行以此类推……
异步编程方案
在同步编程模型中,每个函数总是有序依次地执行,一般上一个函数执行的结果往往是下一个函数的入参,那么在javascript中如何让下一个异步操作等待上一个异步执行得到结果之后在执行呢?
我们已经知道next可以触发生成器执行到yield操作处,并且生成器会在遇到yield时立即执行后面的语句并暂停,那么如果yield后面是一个异步操作,而异步操作获取到结果之后再调用next不就实现了等待的效果么?
function asyncfuc(v) {
setTimeout(function() {
let r = v + 20;
console.log(r);
g.next(r); //把本次的结果传出并触发下一个yield
}, 500);
}
let g = function* gen() {
let v1 = yield asyncfuc(0);
let v2 = yield asyncfuc(v1);
return v2;
}();
g.next();
异步操作执行链
有了前文的基础我们可以实现一个用来执行多个异步操作的函数,定义一个run(...functions)方法依次执行传入的函数,如下:
//这个方法用来模拟一个异步调用
function delay(time, callback) {
setTimeout(function () {
callback(`slept for ${time}`);
}, time);
}
function run(...functions) {
//构造一个生成器循环执行传入的方法
var generator = function* sync(resume, functions) {
let result;
for (var func of functions) {
result = yield func(result, resume); //前一个方法执行的结果作为下一个方法的入参
}
return result;
}(resume, functions);
//提供一个方法用于推进生成器执行。
function resume(callbackValue) {
generator.next(callbackValue);
}
generator.next(); //触发生成器立即执行第一个方法
}
//模拟异步方法调用, 斐波那契数列
function d(result, resume) {
delay(1000, (msg) => {
let value = result;
if (value) {
[value.a, value.b] = [value.b, value.a + value.b];
} else {
value = { a: 0, b: 1 };
}
console.log(value.a);
resume(value);
});
return result;
}
run(d, d, d); //顺序执行异步方法
本文介绍了ES6中的生成器(Generator)概念及其在异步编程中的应用。通过示例展示了yield和next的工作机制,如何使用生成器实现异步操作的执行链。
519

被折叠的 条评论
为什么被折叠?



