解决回调地狱的异步操作,Async 函数是终极办法,但了解生成器和 Promise 有助于理解 Async 函数原理。由于内容较多,分三部分进行,这是第一部分,介绍生成器相关,第二部分介绍 Promise,第三部分介绍 Async 函数。
介绍生成器前先看两个英文单词及对应的中文翻译:
- Generator 生成器
- Iterator 迭代器
1)生成器概念
Generator 是 function,但和普通函数有很大区别。
并且调用 Genrerator 并 不是一次执行完函数体内的全部代码,而是分步骤进行:
- 第一次调用 Generator,不执行函数体里面的代码,返回一个 Iterator 对象,后续 Generator 的调用都是通过 Iterator 对象完成;
- 每一次调用 Iterator 的
next()
方法,都会执行 Generator 里面的代码:
2.1 如果遇到yield
关键字,Generator 非阻塞挂起,同时返回一个对象({value: 'yieldValue', done: false}
),执行步骤 3;
2.2 如果没遇到yield
关键字 Generator 就结束了,那么表示 Generator 执行完毕,同时,也返回一个对象,固定格式{value: undefined, done: true}
; - 当再一次调用 Iterator 的
next()
方法时, Gererator 从前一次挂起的地方接着执行,然后重复步骤 2。
名词解释,“正在等待的 yield”:使 Generator 进入当前挂起状态的 yield 称为 “正在等待的 yield”。Generator 再次执行使从“正在等待的 yield 语句”之后开始执行。这个概念在通过
next()
给 Generator 传参时会用到。
生成器通过在function
关键字后面加一个*
号定义 Generator 函数,如下:
// 定义 generator 函数 |
2)迭代器概念
Iterator 是调用 Generator 后返回一个对象,Iterator 关键属性是next()
方法,触发 Generator 函数体内的代码 执行。同时,还有一个throw()
可以结束 Generator 的执行,同时抛出一个错误
// Iterator 结构 |
3)生成器返回的对象
执行Iterator.next()
后,生成器返回的对象结构如下:
// 如果 Generator 未结束 |
4)完整示例代码
4.1 简单演示
function* genFn() { |
如果结束后继续调用iControl.next()
则一直返回{value: undefined, done: true}
。
Object.prototype.toString.call(iControl)
返回 “[object Generator]"
Object.prototype.toString.call(genFn)
返回 “[object GeneratorFunction]"
4.2 Generator 函数体内含有其他代码
NOTICE:Generator 中如果yield
关键字前后有其他代码,都会正常同步执行,见下面实例代码:
function* genFn() { |
执行上面代码,打印顺序为:
// 第一次调用后挂起 |
4.3 抛出错误
function* genFn() { |
5)遍历 Generator 的输出
5.1 自定义循环方法
通过循环调用 Iterator 的next()
方法可以遍历 Generator 的输出:
function* genFn() { |
5.2 原生封装的方法
可以通过 JS 内置let - of
实现对迭代器对象的值序列的遍历
function* genFn() { |
6)Generator 中包含 Generator
如果 Generator 中含有子 Generator,会进入子 Genretator 中执行,子 Generator 执行完,父 Generator 继续执行,如下:
function* genFn() { |
7)给 Generator 传递参数
可以通过两种形式给 Generator 传参:1,通过给 Generator 函数本身传递参数;2,通过给 Iterator 的next()
方法传参。
7.1 给 Generator 函数传参
通过给 Generator 函数传递参数,在整个 Generator 生命周期中都可以访问,如下:
function* genFn(param) { |
7.2 给 Iterator 的 next() 传参
通过给 Iterator 的next()
传递的参数,会替换正在等待的整个 yield 语句,见下面例子:
function* genFn() { |
注意,因为第一个next()
调用时没有等待的yield
语句,所以第一次调用next()
时传递的参数会被忽略。
如果想在第一次调用
next()
时使用参数,可以给 Generator 函数本身传参
8)Generator 原理简述
- Generator 函数能够 挂起-执行 本质是它的执行上下文在函数执行结束之前不会销毁(_挂起_ 是中间阶段,没有结束,所以上下文在 _挂起_ 的时候不会销毁);
- 但当 Generator 挂起的时候(遇到
yield
关键字)会离开函数调用栈让渡给其他函数,实现非阻塞; - 同时,Generator 返回的 Iterator 对象保存着 Generator 的执行上下文信息,所以可以在 Generator 重新回到函数调用栈时(通过
next()
或者throw()
方法触发)从上次挂起的位置继续执行; - Generator 执行完成,它的上下文随即销毁。
这里的关键在于,Generator 挂起的时候上下文会得到保持,配合yield
及next()
、throw()
的工作,实现 挂起-执行 的循环,直到执行结束。
从调用开始到结束,其生命周期可描述如下:
调用 Generator --> 挂起开始 --> 执行 [--> yield 挂起 --> 执行 --> ...] --> 结束 |
首次调用 Generator 会立即进入挂起等待执行阶段。
Generator 函数的工作机制 很像闭包:返回的 Iterator 对象保持对 Generator 上下文的引用,所以 Generator 的上下文在离开函数调用栈的时候可以保持。
参考资料
[美]JOHN RESIG,BEAR BIBEAULT and JOSIP MARAS 著(2016),Secrets of the JavaScript Ninja (Second Edition),第 6 章 生成器部分,Manning Publications Co.