概念 基本概念 Generator函数是ES6的异步编程解决方案.Generator函数可以理解成一个状态机,封装了多个内部状态.Generator函数有两个特征:一是function与函数名之间有一个星号;二是函数体内部使用yield语句定义不同的内部状态.
1 2 3 4 5 function * helloWorldGenerator ( ){ yield 'hello' yield 'world' return 'end' }
上面的代码定义了一个Generator函数,它有三个状态:hello,world和return语句.
Generator函数的调用方法和普通函数一样,也是在函数名后加上一对括号,但是调用后,函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是Iterator.
调用Iterator对象的next方法,使得指针移向下一个状态.Generator函数是分段执行的,yield语句是函数暂停执行的标记,next方法可以恢复运行.
1 2 3 var hw = helloWorldGenerator ()console .log (hw.next (), hw.next (), hw.next ())
yield表达式 yield语句是暂停标志,遇到yield语句就暂停后面的操作,并将紧跟在yield后的表达式的值作为返回的对象的value属性值.
如果没有yield语句,就一直运行到函数结束,直到return语句为止.
yield语句提供了手动的Lazy Evaluation语法功能.
Generator函数可以不用yield语句,就变成一个单纯暂缓执行函数,只有在调用next方法时才会执行.
1 2 3 4 5 function * f ( ){ console .log ("执行了!" ) } var generator=f ()setTimeout (()=> {generator.next ()},2000 )
yield表达式只能用在Generator函数里,用在其他地方会报错.
与Iterator接口的关系 1 2 3 4 5 function * gen ( ){ } var g=gen ()g[Symbol .iterator ]()===g
next方法的参数 yield语句本身没有返回值,或者说总是返回undefined.next方法可以带有一个参数,该参数会被当作上一条yield语句的返回值.
1 2 3 4 5 6 7 8 9 function * f ( ) { for (let i = 0 ; true ; i++) { var reset = yield i if (reset) { i = 0 } } } var g = f ()console .log (g.next (), g.next (), g.next (true ))
这是一个无限运行的Generator函数,如果next方法没有参数,那么变量reset的值一直为undefined,当next方法带有参数true时,变量reset被重置为true,因而i=-1
1 2 3 4 5 6 7 8 9 10 11 12 function wrapper (generatorFunction ) { return function (...args ) { let generatorObject = generatorFunction (...args) generatorObject.next () return generatorObject } } var wrapped = wrapper (function * () { console .log (`First Input:${yield } ` ) return 'Done' }) wrapped ().next ('hello' )
如果想要在第一次调用next方法就能输入值,需要在Generator函数外面再包一层.
for…of循环 for…of循环可以自动便利Generator函数生成的Iterator对象,且此时不再需要调用next方法.
1 2 3 4 5 6 7 8 9 10 11 function * foo ( ) { yield 1 yield 2 yield 3 yield 4 yield 5 return 6 } for (const iterator of foo ()) { console .log (iterator) }
用Generator函数和for…of循环实现Fibonacci数列
1 2 3 4 5 6 7 8 9 10 11 12 13 function * fibo ( ) { let [prev, curr] = [0 , 1 ]; for (; ;) { [prev, curr] = [curr, prev + curr] yield curr } } for (const n of fibo ()) { if (n > 1000 ) { break } console .log (n); }
利用for..of循环,为不具有Iterator接口的对象添加遍历器接口.
1 2 3 4 5 6 7 8 9 10 11 12 13 function * objectEntries ( ) { let propKeys = Object .keys (this ) for (const propKey of propKeys) { yield [propKey, this [propKey]] } } let foo = { first : 'foo' , last : 'bar' }foo[Symbol .iterator ] = objectEntries for (const [k, v] of foo) { console .log ([k, v]) }
Generator.prototype.throw() Generator函数返回的遍历器有一个throw方法,可以在函数体外抛出错误,然后再函数体内捕获.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function * g ( ) { try { yield } catch (e) { console .log ("内部捕获" , e) } } var i = g ()i.next () try { i.throw ('a' ) i.throw ('b' ) } catch (e) { console .log ('外部捕获' , e); }
第二次抛出错误的时候,由于Generator函数内部的catch语句已经执行过了,所以不会捕捉到错误,被函数体外的catch捕获
1 2 3 4 5 6 7 8 9 10 11 12 function * foo ( ) { var x = yield 3 var y = x.toUpperCase () yield y } var bar = foo ()bar.next () try { bar.next (42 ) } catch (e) { console .log (e) }
Generator函数体内抛出的错误可以再函数体内捕获,反过来,Generator函数体内抛出的错误也可以在函数外捕获.
Generator.prototype.return() Generator函数返回的遍历器有一个return方法,可以返回给定的值,并总结Generator函数的遍历
1 2 3 4 5 6 7 8 function * foo ( ) { yield 1 yield 2 yield 3 } var bar = foo ()console .log (bar.next (), bar.return ());
yield* 如果要再Generator函数里调用另一个Generator函数,就要用yield*语句
1 2 3 4 5 function * foo ( ) { yield 1 yield 2 }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function * bar ( ) { yield 'x' yield * foo () yield 'y' } function * bar ( ) { yield 'x' yield 1 yield 2 yield 'y' } function * bar ( ) { yield 'x' for (let v of foo ()){ yield v } yield 'y' }
Generator函数的异步应用 异步任务的封装 1 2 3 4 5 6 7 8 9 10 11 12 13 const fetch = require ('node-fetch' );function * gen ( ) { var url = 'https://www.baidu.com' var res = yield fetch (url) console .log (res) } var g = gen ()var res = g.next ()res.value .then (data => { return data }).then (data => { g.next (data) })
Fetch模块返回的是Promise对象,要用then方法调用下一个next方法,虽然简洁,但是流程管理不便.
Thunk函数 Thunk函数是自动执行Generator函数的方法
1 2 3 4 5 6 7 8 const fs = require ('fs' );var Thunk = function (fileName ) { return function (callback ) { return fs.readFile (fileName,callback) } } var readFileThunk = Thunk (fileName)readFileThunk (callback)
任何函数,只要参数有回调函数,就能写成Thunk函数形式
Thunk函数转换
1 2 3 4 5 6 7 const Thunk = function (fn ) { return function (...args ) { return function (callback ) { return fn.call (this , ...args, callback) } } }
一个例子
1 2 3 4 5 function f (a, callback ) { callback (a) } var ft = Thunk (f)ft (1 )(console .log )
Thunkify模块
使用方法
1 2 3 4 var thunkify=require ('thunkify' )var fs=require ('fs' )var read=thunkify (fs.readFile )read ('test.txt' )(callback)
Generator函数流程管理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const fs = require ('fs' );const thunkify = require ('thunkify' );var read = thunkify (fs.readFile )var gen = function * () { var r1 = yield read ('./a.txt' ) console .log (r1.toString ()) var r2 = yield read ('./b.txt' ) console .log (r2.toString ()) } var g = gen ()var r1 = g.next ()r1.value ((err, data ) => { if (err) { throw err } var r2 = g.next (data) r2.value ((err, data ) => { if (err) { throw err } g.next (data) }) })
Generator函数的执行过程其实是将同一个回调函数反复传入next方法的value属性.可以用递归来自动完成这个过程
Thunk函数的自动流程管理 下面是一个基于Thunk函数的Generator执行器的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function run (fn ) { var gen = fn () function next (err, data ) { var res = gen.next (data) if (res.done ) { return } result.value (next) } next () } function * g ( ) { var f1 = yield readFile ('A' ) var f2 = yield readFile ('B' ) var f3 = yield readFile ('C' ) } run (g)
函数g封装了n个异步的读取文件操作,只要执行run函数,这些操作就会自动完成,这样一来,异步操作不仅可以写得像同步操作,而且只需要一行代码就可以执行
co模块 用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 const fs = require ('fs' );const co = require ('co' );const thunkify = require ('thunkify' );var readFile = thunkify (fs.readFile )var gen = function * () { var f1 = yield readFile ('./a.txt' ) var f2 = yield readFile ('./b.txt' ) console .log (f1.toString ()) console .log (f2.toString ()) return 'Done' } co (gen).then ((res ) => { console .log (res); })
使用co模块无需编写Generator函数执行器,co函数返回一个Promise,可以添加回调函数
处理并发的异步操作 co支持并发的异步操作,就是允许某些操作同时进行,等到它们全部完成才进行下一步
这时要把并发的操作放在数组或者对象里,放在yield语句后面
1 2 3 4 5 6 co (function * () { var res = yield [Promise .resolve (1 ), Promise .resolve (2 )] console .log (res) }).catch (err => { console .log (err) })
1 2 3 4 5 6 7 8 9 10 11 12 const fs = require ('fs' );const co = require ('co' );const thunkify = require ('thunkify' );var readFile = thunkify (fs.readFile )co (function * () { var values = [1 , 2 , 3 ] res = yield values.map (time2) console .log (res); }) function time2 (v ) { return v * 2 }