Generator 函数 Generator
(生成器)函数是 ES6
提供的新的解决异步回调嵌套的特性,先看个实例:
function * test ( ) { yield 1 ; yield 2 ; return 3 ; } var g = test(); var rt;rt = g.next(); console .log(rt); rt = g.next(); console .log(rt); rt = g.next(); console .log(rt); rt = g.next(); console .log(rt);
上面出现的几个关键字的作用:
function*
声明一个生成器函数,通过生成器函数可以得到一个迭代器
yeild
关键字可以保存 Generator 函数的执行位置,并跳出到调用此函数的地方继续执行, yeild 关键字只能用于 Generator 函数, 否则报错
通过此函数的 next
函数,可以继续返回之前暂停的地方继续执行,next 函数返回值是一个包含两个属性的对象,value
属性是 yield
后面语句的结果,done
属性代表是否结束
Generator 的运行流程为:
每次执行生成器函数(var g = test()
),都会生成一个迭代器对象(g
)
每次调用迭代器对象的 next 方法(g.next()
),都会促使迭代器对象向下执行,直到遇到 yield
关键字后,保存当前状态并跳出迭代器,并把 yield
关键字后的表达式的结果作为 value
的值返回
外围函数继续执行(console.log(rt)
),直到下次调用迭代器的 next 函数,则继续从迭代器停止的位置开始继续执行,直到下一个 yield
语句。
如果直到运行结束没有下一个 yield
语句则迭代器结束,设置 done
字段的值为 true
,此时,如果存在 return
语句,则 return
语句的返回值为 value
属性的值,否则 value
的值为 undefined
此时迭代器已经结束,再次调用 next()
结果始终为: {value: undefined, done: true}
Generator.prototype.next() next()
方法可以接受一个参数向生成器传值(参数可以省略),并返回一个包含属性 done
和 value
的对象:
gen.next([value])
[value][Any=undefined]: 修改上一次 yield
语句的返回值,如果是第一次调用则不起作用。
return[{value, done}]: value
表示本次 yield
语句的值,done
表示是否结束。
注意 如果 value
参数省略则传递给当前 yield
语句的值为 undefined
:
function * double (x ) { while (true ) { x = 2 * (yield x); } } var t = double(3 );t.next() t.next() t.next(4 )
在以上示例中,第二次执行 next()
未传递参数,此时实际上等价于 x = 2 * undefined
因此此时 value
属性的值为 NAN
,第三次执行时,传递了参数 4
此时的 value
为 2 * 4
。
Generator.prototype.throw() throw()
方法用来向生成器抛出异常,如果生成器函数包含 try...catch
语句块,则被其捕获,否则异常抛到外部函数:
var g = function * ( ) { try { yield ; } catch (e) { console .log('内部捕获' , e); } yield console .log("This line will be logged" ); }; var i = g();i.next(); try { i.throw('a' ); i.throw('b' ); } catch (e) { console .log('外部捕获' , e); }
由上述示例可以看到,在触发 throw
语句时,如果 generate
内部处理了这个错误,则会执行了一次 next
语句。
刚才提到,如果 generate 函数跑出的错误没有被 generate 函数内部捕获处理,错误会被交给外部函数,此时,生成器函数将执行结束,即再次调用其 next
方法,则始终返回 {value:undefined, done: true}
,此生成器函数不再会被执行:
var gen = function * ( ) { yield console .log("This line will be logged." ); throw new Error ("Something is error." ) yield console .log("This line will not be logged." ); yield console .log("This line also will not be logged." ); }; var g = gen();try { g.next(); g.next(); g.next(); } catch (err) { console .log(err) }
Generator.prototype.return() return()
方法返回给定的值并提前结束生成器
function * gen ( ) { yield 1 ; yield 2 ; yield 3 ; } var g = gen();g.next(); g.return("foo" ); g.next();
Async 函数 ES2017
标准引入了 async
函数,更加简化了异步操作,而 async
函数实际上是 Generator
函数的语法糖。
async 函数返回一个 Promise
对象,可以使用 then
回调处理函数的返回值,async
函数如果包括 await
表达式,则会暂停执行,等待 await
表达式执行完毕后继续执行,如果 async
函数内部有多个 await
,则只有当所有的 await
执行完成或者遇到未捕获的错误才会结束。
function delay (time, value ) { return new Promise (resolve => { setTimeout (() => { console .log(`After ${time} ms return ${value} .` ); resolve(value); }, time) }) } async function add (x ) { var a = await delay(200 , 1 ); var b = await delay(300 , 2 ); return x + a + b; } add(2 ).then(d =>console .log(d));
await 语句 await 命令通常跟一个 Promise
对象,如果后面不是一个 Promise,则被转换成一个立即 resolve
的 Promise
对象,如果 await 后面的 Promise
状态变为 reject
,则 async 停止执行,且其返回的 Promise
状态为 reject
:
async function test ( ) { await Promise .reject("reject" ); console .log("This line will not run" ); } test().then(d => console .log(d)).catch(e => console .log(e));
await 并发 多个 await
的命令实际上是逐个调用的,即只有在上一个调用完成之后才会进行下一个的调用:
function delay (time, value ) { return new Promise (resolve => { setTimeout (() => { console .log(`After ${time} ms return ${value} .` ); resolve(value); }, time) }) } async function add (x ) { var a = await delay(300 , 1 ); console .log('Waiting.' ); var b = await delay(200 , 2 ); return x + a + b; } add(2 ).then(d =>console .log(d));
由以上示例可以看到,第一个 await
执行时间较长,log('Waiting.')
语句等待其执行完毕后才执行,如果两个 await 是相互独立的操作最好让他们同时触发,可以减少操作耗时:
function delay (time, value ) { return new Promise (resolve => { setTimeout (() => { console .log(`After ${time} ms return ${value} .` ); resolve(value); }, time) }) } async function add (x ) { var t1 = new Date ().getTime(); var a = await delay(300 , 1 ); var b = await delay(200 , 2 ); var t2 = new Date ().getTime(); console .log(`Async run Time is ${t2 - t1} ms.` ); return x + a + b; } async function add1 (x ) { var t1 = new Date ().getTime(); var delayA = delay(300 , 1 ); var delayB = delay(200 , 2 ); var a = await delayA; var b = await delayB; var t2 = new Date ().getTime(); console .log(`Async run Time is ${t2 - t1} ms.` ); return x + a + b; } async function add2 (x ) { var t1 = new Date ().getTime(); var [a, b] = await Promise .all([delay(300 , 1 ), delay(200 , 2 )]) var t2 = new Date ().getTime(); console .log(`Async run Time is ${t2 - t1} ms.` ); return x + a + b; } add(2 ).then(d =>console .log(d)); add1(2 ).then(d =>console .log(d)); add2(2 ).then(d =>console .log(d));
由上例可以看出,此时耗时更短的 delayB
先完成,并且总耗时也最短。
重写 Promise回调链: 先看一个示例:
function getProcessedData (url ) { return downloadData(url) .catch(e => { return downloadFallbackData(url) }) .then(v => { return processDataInWorker(v); }); } async function getProcessedData (url ) { let v: try { v = await downloadData(url); } catch (e) { v = await downloadFallbackData(url); } return processDataInWorker(v); }
从上述示例可以看出,使用 Async
处理异步的流程写起来非常的直观,就像同步的代码一样。
异常处理 如果 await
后面的 promise 出错或者 reject
,此 await 后面的语句将会被跳过,此时可以将 await
语句放到 try...catch
代码块中:
async function test ( ) { try { await Promise .reject("reject" ); } catch (e) { } console .log("This line will run." ); } test().then(d =>console .log(d)).catch(e =>console .log(e));
使用 try...catch
结构,可以实现多次尝试:
function delay (time, value ) { return new Promise ((resolve, reject ) => { setTimeout (() => { var random = Math .random(); if (random > 0.6 ) { resolve(value); } else { reject(value) } }, time) }) } async function test ( ) { const MAX_TRY_TIMES = 5 ; for (var i = 0 ; i < MAX_TRY_TIMES; i ++) { console .log(`第 ${i + 1 } 次尝试` ) try { await delay(100 , 1 ); break ; } catch (err) { } } console .log(`第 ${i + 1 } 次尝试成功` ); } test();
Async 的实现 前文提到 async
是 generate
函数的语法糖,也就是说,async
函数实质上是对 generate
函数的封装,在这里将尝试使用 generate
来生成 async 的实现:
首先需要把 async
的函数内容定义转化为 generate
函数,这一步很简单,只需要把 async
关键字去掉,function
关键字后面加 *
,代码中所有的 await
关键字改为 yield
关键字:
var asyncFunc = async function (x ) { var a = await delay(300 , 1 ); var b = await delay(200 , 2 ); return x + a + b; } var generateFunc = function * (x ) { var a = yield delay(300 , 1 ); var b = yield delay(200 , 2 ); return x + a + b; };
但是此时 generateFunc
还不能被执行,还需要为 generateFunc
创建一个执行器 generateFuncRunner
, 而 async 函数返回一个 Promise
,可以很容易写出执行器 generateFuncRunner
的函数签名:
var asyncFunc = function ( ) { return generateFuncRunner(generateFunc); }; function generateFuncRunner (fn ) { return new Promise (function (resolve, reject ) { }); }
接下来就是编写一个 generate
的运行器了,具体思路在注释里,不再详细说明:
function generateFuncRunner (fn ) { return new Promise (function (resolve, reject ) { var gen = fn(); function step (args ) { try { var next = gen.next(args); } catch (err) { reject(err); } if (next.done) { resolve(next.value); } else { return Promise .resolve(next.value).then(function (value ) { step(value); }, function (err ) { gen.throw(err); }) } } }); }
这样就实现了 async
函数,下面附上 babel
对 async
的转码作为参考(针对本实例稍有修改):
let asyncFunc = (() => { var _ref = _asyncToGenerator(function * (x ) { var a = yield delay(300 , 1 ); var b = yield delay(200 , 2 ); return x + a + b; }); return function asyncFunc ( ) { return _ref.apply(this , arguments ); }; })(); function _asyncToGenerator (fn ) { return function ( ) { return new Promise (function (resolve, reject ) { var gen = fn(); function step (key, arg ) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error);return ; } if (info.done) { resolve(value); } else { return Promise .resolve(value).then(function (value ) { step("next" , value); }, function (err ) { step("throw" , err); }); } }return step("next" ); }); }; }
参考