ES6 异步编程之一:Generator
2017-02-08 20:22
225 查看
Generator
生成器是es6原生提供的异步编程方案,其语法行为和传统函数完全不同,阮大的《ECMAScript 6 入门》一书中对生成器有比较详尽的介绍,还有一些其他的文章可以参考,比如:《ECMAScript 6 入门:generator》
深入浅出ES6(三):生成器 Generators
深入浅出ES6(十一):生成器 Generators,续篇
本文主要是通过一些代码示例来记录和总结生成器的用法。
yield 和 next
yield和
next在生成器中扮演着非常重要的角色,前者是一个操作符,后者是生成器上的一个函数。
他们具有以下特性:
需要调用generator的
next函数,生成器中的语句才开始执行;
next函数在生成器之外调用,意味着可以在生成器之外控制其内部操作的执行过程;
当生成器执行到
yield操作符就立即执行
yield之后的语句并暂停,不敢妄言内部原理,姑且感性地比作savepoint;
当再次调用生成器的
next函数时,生成器从上次发生
yield的'savepoint'继续执行,直到再次遇到
yield,或者遇到是
return或者
throw生成器就退出;
next的返回值是一个形如
{done:false, value:x}的对象,每次调用
next都会使生成器继续执行,对于
next的返回值有如下规律:
如果再次遇到
yield,
next返回值中的
value属性是紧接在这条
yield之后的语句执行之后的返回值;
如果遇到的是
return,那么返回对象
done=true,
value则是
return的返回值;
其他情况下,返回对象
{done:false, value:undefined};
next的输入参数在上一次发生
yield的地方返回,所以第一次调用
next传入的参数是“然并卵”,
next是在生成器之外调用的,所以这个机制使得我们有能力控制生成器内部的行为。
以上说了很多,先看一个用生成器实现的一个无限斐波那契数列,可以无限的调用
next函数,他永远不会新视觉返回done=true
const f = function* fibonacci() { let [a, b] = [0, 1]; for (;;) { yield a; [a, b] = [b, a + b]; } }(); //执行三次,得到三个对象,其value值分别是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才返回
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); //顺序执行异步方法
以上实现有个值得注意的地方是通过一个叫做的
resume的函数来推进生成器,它要求业务处理函数必须带上一个
resume并在获得异步结果之后把结果作为参数并执行
resume(value)(如同上面的
d(result, resume)函数),由于
resume不是一个标准的api,因此这个方案具有比较大的侵入性。
相关文章推荐
- 用 ES6 generator & Promise 写异步代码
- ES6 generator 与 koa 中间件 是如何 generator解决异步的
- ES6中Generator与异步操作实例分析
- es6 用generator进行异步方法同步
- ES6-Generator与异步操作
- 《深入理解ES6》阅读笔记 --- Promise与异步编程
- 异步编程之Generator(1)——领略魅力
- es6 Generator与异步的同步书写
- 5、ES6 === 异步 Promise Generator yield
- 用ES6 Generator替代回调函数
- C#异步编程之async、await
- 基于Socket的多线程和异步非阻塞模式编程
- 绑定窗口的异步套接字编程
- ES6__异步开发优化
- 深入理解JavaScript编程中的同步与异步机制
- ES6的异步操作
- Async异步队列编程模型
- socket编程的同步、异步与阻塞、非阻塞示例详解之一
- co核心代码剖析--promise与ES6 generator结合
- 详解ASP.NET异步和Windows Shell 外壳编程系列7-ContextMenu 注册文件右键菜单