一、什么是 Generator
The Generator object is returned by a generator function and it conforms to both the iterable protocol and the iterator protocol.
生成器对象是由一个 generator function 返回的,并且它符合可迭代协议和迭代器协议。
可迭代协议
可迭代协议是指允许 JavaScript 对象(可迭代对象)去定义或定制它们的迭代行为,这个对象(或其原型链中的任意一个对象)必须具有一个带 Symbol.iterator
键的属性,其值返回一个对象的无参函数,被返回对象符合迭代器协议
迭代器协议
迭代器协议定义了一种标准的方式来产生一个有限或无限序列的值,并且当所有的值都已经被迭代后,就会有一个默认的返回值
迭代器需要实现 next() 方法,返回一个对象的无参函数,被返回对象拥有两个属性,分别是 (done :Boolean) 迭代器已经超过了可迭代次数时为 true,这时 value 的值可以被省略,除非该迭代器 return 了某个值
1 | var iterable = { |
生成器函数
生成器函数的定义和普通函数类似,只是需要在函数名前关键字 function 后添加 * 号 funtion* generator(){},需要注意的是,生成器函数不能作为构造函数
调用生成器函数后会返回一个生成器对象,该对象是个可迭代对象,同时实现了 next 方法,所以也是一个迭代器,即生成器既是迭代器也是可迭代对象
yield 表达式就是用来表示迭代暂停的位置
1 | function* demo(d) { |
从上面的例子我们能看出来,执行 next() 的时候,代码的运行会到 yield 关键字的右侧,这时返回的值就是 yield 右侧的语句执行的结果,而 next() 中的传参则会被赋值到 yield 左侧,即作为 yield 的返回值使用。
如果生成器对象调用 throw 方法,那么能够被 tryCatch 捕获到,并且在 catch 的 body 中可以继续执行
如果生成器对象调用 return 方法,那么函数立即返回,同时后续的 yield都不会被执行,等同于函数在该语句的位置被 return 了
yield 和 yield*
yield* 表达式用于委托给另一个可迭代对象
1 | function* g1() { |
从上面的例子可以看出,yield* 的作用实质上是解决嵌套可迭代对象(arguments,array,string,set,map 等都是可迭代的)的问题,更像是个语法糖,同时其 yield 还有一个区别是它是表达式,所以会有自己的返回值,而 yield 的返回值由 next() 参数所决定。
二、意义(解决什么问题)
协程与化异步为同步写法
与协程相关的是进程、线程,进程是计算机资源分配的基本单位,线程是执行的基本单位。
计算机将存储资源分配给进程之后,进程就可以利用这些资源进行计算,进程可以生成线程去执行语句,线程间可以使用共享内存进行通讯,而协程则是更精细化的资源管理。为了防止某一进程占用过多资源,Cpu 有一套调度算法,随着粒度越细,切换的时候的消耗就越小,协程则可以理解成是在某个线程中的多线程而这个切换过程是无需系统参与的,也就少了系统调用,同时也是在同一个栈上切换的,例如 Go 的 coroutine 就是占用更少的资源,进行更精细的调度。
generator 的暂停执行特性,可以将函数执行的控制权交出,JS 的单线程特性使得可以利用 generator 实现协程,其中的关键在于实现流程自动调度功能。
JS 是运行在单线程中的,为了提高效率,引入了异步方式,当运行一些远程 IO 或者网络请求的时候可以无需等待直接往下执行,等拿到响应之后再执行回调。这就引入了一个典型的问题,就是回调嵌套地狱(callback hell)。
当引入了 generator 后,异步的代码就可以写成同步的方式,降低认知成本,提高代码可读性
1 | doAsync1(str, res1 => { |
三、实践和库( co 和 redux-saga)
co 和 redux-saga 的核心代码是类似的,都实现了一套流程自动调度,redux-saga 在此基础上添加了更多的功能
1 | const it = gen(); |
co
co 库主要做的一件事情就是为 generator 包裹一层 promise,然后通过 then 将控制权返回,同时对于 generator 实现了流程的自动调度,对于对象和数组类型还支持并发调用。
1 | // 将对象的value值转化为promise数组 在用Promise.all实现并发 |
redux-saga
1 | import createSagaMiddleware from "redux-saga"; |
1 | // redux-saga/packages/core/src/internal/middleware.js |
1 | //redux-saga/packages/core/src/internal/runSaga.js |
1 | //redux-saga/packages/core/src/internal/proc.js |