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" ); }); }; }
参考