前几天学习 for…of 和 iterator 对象的时候接触到了 generator 函数,generator函数返回的是一个 iterator 对象,而 通过[Symbol.iterator]
接口返回的 iterator 对象,就是 for…of 遍历时的规则,所以,今天来学习一下这个 generator 函数
复习:iterator对象
迭代器对象,结构为:
1 | { |
如果想要让某个对象能够使用 for...of
进行遍历
1 | var obj = { |
generator 函数
generator 函数返回的就是一个 iterator 对象,也就是类似于
1 | { |
的一个对象
基本语法
1 | function *gen() { |
在函数 function 之后使用 *
声明这是一个 generator 函数,其实就是一种可以暂停执行的函数,函数内部的 yield 就是暂停点,每次都等到 yield 后面的部分返回值之后,函数才会继续向下执行
Generator 函数返回的就是一个 遍历器对象,它的暂停执行,也是通过 遍历对象的next() 实现的,遍历器对象的next
方法的运行逻辑如下。
(1)遇到yield
表达式,就暂停执行后面的操作,并将紧跟在yield
后面的那个表达式的值,作为返回的对象的value
属性值。
(2)下一次调用next
方法时,再继续往下执行,直到遇到下一个yield
表达式。
(3)如果没有再遇到新的yield
表达式,就一直运行到函数结束,直到return
语句为止,并将return
语句后面的表达式的值,作为返回的对象的value
属性值。
(4)如果该函数没有return
语句,则返回的对象的value
属性值为undefined
。
yield 后面的代码,只有在调用 next ,执行到这一行的时候,才会开始执行(手动的惰性求值)
yield*
指后面跟着的也是一个 generator 函数的执行结果(即一个 iterator 对象),这一步的执行相当于 把后面的generator函数平铺开来,对其进行遍历
1 | function* concat(iter1, iter2) { |
next/return/throw
next()
、throw()
、return()
这三个方法本质上是同一件事,可以放在一起理解。它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换yield
表达式。
next()
是将yield
表达式替换成一个值。
1 | const g = function* (x, y) { |
上面代码中,第二个next(1)
方法就相当于将yield
表达式替换成一个值1
。如果next
方法没有参数,就相当于替换成undefined
。
throw()
是将yield
表达式替换成一个throw
语句。
1 | gen.throw(new Error('出错了')); // Uncaught Error: 出错了 |
return()
是将yield
表达式替换成一个return
语句。
1 | gen.return(2); // Object {value: 2, done: true} |
generator 函数的返回值
generator 函数的返回值是一个 指针对象(也就是一个 iterator 对象)
generator 中的this
普通 generator 函数中的 this 是不生效的,即使使用 new 来创建,generator 返回的也是 iterator 对象,每次调用 iter.next() 时,generator函数中的this并不是指向 iter,而是指向其上级作用域链中的 this 对象
1 | var obj = { |
上面代码中,虽然执行 next 的对象是 iter,但是执行时其中的 this 指向的是 obj,也就是其上级作用域链中的 this
而且不能执行 new
1 | function* F() { |
generator 异步
1 | var fetch = require('node-fetch'); |
上面代码中,首先执行 Generator 函数,获取遍历器对象,然后使用next
方法(第二行),执行异步任务的第一阶段。由于Fetch
模块返回的是一个 Promise 对象,因此要用then
方法调用下一个next
方法。
那么,如何让函数自执行,也就是自己在上一次yield返回数据之后,调用 next 从而进行接下来代码的执行呢
既然 generator 函数的初衷是为了把执行权暂时出让,这里又为什么要探究自执行的机制呢?
在这里,generator 函数更大的作用是为了在 generator 函数内部,可以用 同步的方式来写异步的代码。
因为generator 函数能够出让执行权的时机都是每次 yield 执行结束,返回数据的时间点,而在yield 后面的函数执行过程中是没有办法被打断的,我们让 generator 函数自执行,那么在这个 generator 函数内部,我们可以保证,每次 yield 后的函数不会被打断,而且在这个函数执行的过程中这个generator函数会处于等待状态,而且 yield 一旦返回数据,函数就可以立即执行接下来的同步代码
协程的概念,在异步的时候把执行权交出给这个异步函数,异步函数一旦执行完毕,就把执行权收回来继续执行同步代码
自执行函数的实现——thunk 函数
什么是 thunk 函数
如果某个函数调用中包括一个回调函数,也就是说,在处理完这个函数的主流程之后,将调用这个回调函数进行一些本函数之外数据或状态的变更,那么这样的函数可以被包装成一个 thunk 函数
1 | function thunk(fn) { |
call 和 apply 的区别,apply 的调用参数为数组,而 call 则直接传入即可
thunk 函数的作用,就是延迟 callback 的传入时机
generator 与 thunk 函数
那么,generator 函数又与 thunk 函数有什么关系呢?——使用 Thunk 函数,可以自执行 generator 函数
1 | function run(g) { |
调用:
1 | var g = function* (){ |
自执行函数的实现——Promise
使用Promise 实现,则需要 generator 函数中每个yield 后面跟着的都必须是一个 Promise 对象
1 | function run(g) { |