generator 是什么?
generator 是 ES6 提供的一种异步编程解决方案, 在语法上, 可以把它理解为一个状态机, 内部封装了多种状态执行 generator, 会生成返回一个遍历器对象返回的遍历器对象, 可以依次遍历 generator 函数的每一个状态同时 ES6 规定这个遍历器是 Generator 函数的实例, 也继承了 Genarator 函数的 prototype 对象上的方法
最简单的 generator 函数, 其实它就是一个普通的函数, 但是它有两个特征第一就是 function 关键字与函数名之间有一个 * 号, 其二就是函数体内使用 yield 表达式来遍历状态:
- function* newGenerator(){
- yield 'hello';
- yield 'world';
- return 'ending';
- }
执行 generator 函数之后, 该函数并不会立即执行, 返回的也不是函数运行结果, 而是一个指向内部状态的指针对象通常使用遍历器对象的 next 方法使得指针移向下一个状态每一次调用 next()方法, 内部指针就从函数头部或上一次停下里的地方开始执行, 直到遇到下一个 yield 表达式位置, 由此可以看出, generator 是分段执行的, yield 表达式是暂停执行的标记, 而 next 方法可以恢复执行
generator 中的 yield 表达式
yield 表达式在 generator 中是作为一个暂停标志, 当碰到 yield 时, 函数暂停执行, 等到下一次 next()执行时, 函数才从当前 yield 位置开始执行并且, yield 表达式只能用在 Generator 函数里边; 同时, yield 如果后边带一个, 则就是相当于一个 for...of 的简写形式, 如果 yield 后边不带, 则返回的是 generator 的值
- function* gen() {
- yield 'hello';
- yield* 'hello';
- }
- let f = gen();
- console.log(f.next().value);
- console.log(f.next().value);
- console.log(f.next().value);
- console.log(f.next().value);
- console.log(f.next().value);
上述例子中的后四个 next()函数, 就会顺序的返回 h e l l
generator 中的 next 函数
通过 next 函数, 可以执行对应的 yield 表达式, 且 next()函数还可以带参数, 该参数可以作为上一次 yield 表达式的返回值, 因为 yield 本身是没有返回值的, 如果 next()中不带参数, 则 yield 每次运行之后的返回值都是为 undefined;
- function* dataConsumer() {
- console.log('Started');
- console.log(`1. ${yield}`);
- console.log(`2. ${yield}`);
- return 'result';
- }
- let genObj = dataConsumer();
- genObj.next();
- // Started
- genObj.next('a');
- // 1. a
- genObj.next('b');
- // 2. b
上述函数中, 第一次运行 next(), 运行到第一个 next()函数截止, 第二个 next 运行时, 传入的参数为'a'; 则运行到第二个 yield 地方截止, 然后第一个 yield 运行的返回值为'a', 依次类推, 则得到上述结果
另外, 通过 for...of 可以循环 generator 中的所有状态, 并且不需要使用 next()函数除了 for...of 循环以外, 扩展运算符(...), 解构赋值和 Array.form 方法内部调用的, 都是遍历器接口
generator 生成的对象, 还有其他一些函数, 比如 throw()用来抛出错误, return()用来定义返回值并终止 generator 的状态
以上的三个方法在本质上其实是一样的, 他们就是让 generator 恢复执行, 并且使用不同的语句来替代 yield 语句
next()是将 yield 表达式替换成一个值
throw()是将 yield 表达式替换成一个 throw 语句
return()是将 yield 表达式替换成一个 return 语句
Generator 与协程
协程可以理解为协作的线程或者协作的函数协程既可以是单线程实现, 也可以用多线程实现, 前者是一种特殊的子例程, 后者是一种特殊的线程
协程有点像函数, 又有点像线程, 它的运行流程大致如下
第一步, 协程 A 开始执行
第二部, 协程 A 执行到一半, 进入暂停, 执行权转移到协程 B
第三步,(过了一段时间)协程 B 交换执行权
最后, 协程 A 恢复执行
协程适合用于多任务运行环境, 它与普通的线程很相似, 都有自己的执行上下文, 可以分享全局量他们的不同之处在于, 同一时间可以有多个线程处于运行状态, 但是运行的协程只能有一个, 其他协程都是处于暂停状态
由于 JavaScript 是单线程, 只能保持一个调用栈, 引入协程之后, 每一个任务可以保持自己的调用栈, 这样就可以再抛出错误的时候找到原始的调用栈, 不至于像异步操作的回调函数那样, 一旦出错, 原始的调用栈早就结束了
Generator 函数是 ES6 对协程的实现, 但属于不完全实现 Generator 函数被称为半协程(semi-coroutine), 意思是只有 Generator 函数的调用者, 才能将程序的执行权还给 Generator 函数如果是完全执行的协程, 任何函数都可以让暂停的协程继续执行
如果将 Generator 函数当做协程, 完全可以将多个需要互相协作的任务写成 Generator 函数, 他们之间使用 yield 标识交换控制权
Generator 函数执行产生的上下文环境, 一旦遇到 yield 命令, 就会暂时退出堆栈, 但是并不消失, 里面的所有变量和对象会冻结在当前状态等到对它执行 next 命令时, 这个上下文环境又会重新加入调用栈, 冻结的变量和对象恢复执行
Generator 函数的多种用途
可以使异步操作来实现同步化表达
控制流管理
部署 Iterator 接口
做为数据结构
上述的介绍中, 我们看到了 generator 是什么, 下边我们看一下, 目前中, 我们使用最多的, generator 函数的异步调用
异步编程对于单线程的 JavaScript 无疑是十分重要的 (可以笼统的将异步定义为不连续的执行) 之前的文章中, 我们也说过, JavaScript 中对于异步的实现, 就是回调函数我们之前也有使用过 promise 去进行死亡回调的改良, promise 来使回调嵌套的表现形式更好了些其实呢 generator 函数也可以用来实现异步回调的嵌套
整个 Generator 函数就是一个封装的异步任务, 或者说是异步任务的容器异步操作需要暂停的地方, 都用 yield 语句注明除此之外, 它还有两个特性, 使它可以作为异步编程的完整解决方案: 函数体内外的数据交换和错误处理机制即为 next 方法还可以接收参数, 向 Generator 函数体内输入数据
Thunk 函数
Thunk 函数是自动执行 Generator 函数的一种方法
对于以前的函数参数的求值计算, 有两种计算方式, 一种是传值调用, 一种是传名调用编辑器中的传名调用的实现, 往往是将参数放到一个临时函数之中, 再将这个临时函数传入函数体, 这个临时函数就叫做 Thunk 函数
JavaScript 中使用的是传值调用, 它的 Thunk 函数含义有所不同再 JavaScript 中, 替换的不是表达式, 而是多参数函数的休整
Thunk 函数可以用于 Generator 函数的自动流程管理即使 Generator 函数可以自动执行
co 模块
co 模块用于 Generator 函数的自动执行 co 函数返回一个 Promise 对象, 因此可以用 then 方法添加回调函数
使用了 Generator 函数之后, 我们可以将多个异步嵌套的代码改为同步写异步, 大大的简化了我们的代码量, 以及代码的美观
上边就是大概的 ES 中的 Generator 函数的介绍, 我们可以使用 Generator 来实现 lazy evaluation,Iterator 和实现异步调用同步的写法, 但是其中我们面临的更多的还是 generator 的执行问题, 下一篇我们来看一下 generator 是怎么实现 async/await 来控制异步以及 co 模块的实现
来源: https://www.cnblogs.com/blackgan/p/8630699.html