我们知道JS是单线程的,IO操作都被设计为异步的(非阻塞的),对于异步操作我们必须要写callback function来解决异步操作的返回结果。
当要写n个串行的IO操作(上一个IO结束后才继续下一个)时, 不得不写n个回调函数:
get('/car/1', car =>{ get(`/seats/${car.seat}`, seat => { get(`/colors/${seat.color}`, color => { //...more_IOs }) })});
n个串行的IO操作,以上代码(都写成匿名回调函数的话)会出现n层嵌套, 可以采用Promise 改进了代码嵌套(callback hell)的问题:
promiseGet('/car/1') .then(car => promiseGet(`/seats/${car.seat}`)) .then(seat => promiseGet(`/colors/${seat.color}`)) .then(color => {/*return some promise*/}) .then(...)
Promise加回调函数似乎已经足够简洁了,由于es6里arrow function使得写一个匿名函数变得很简洁。
然而假如使用 es7 async/await, 我们连回调函数都不用写了:
// promise version:let v1, v2;promise1() .then(v => {v1=v; return promise2(v1)}) .then(v => {v2=v; return promise3(v1,v2)}) .then(v3 => { /*..v1,v2,v3...*/ });// async/await version:async function asyn_run(){ let v1 = await promise1(); let v2 = await promise2(v1); let v3 = await promise3(v1, v2); /*... v1, v2, v3 ... */}async_run(); //actually non-blocking
注意async function仍然是非阻塞的异步执行,async/await只是语法糖,也就是相当于编译器替我们写了回调函数!
async/await 其实被编译成了 generator函数(function*(){...})):
async function fn(args){ let v1 = await promise1(); let v2 = await promise2(v1); ...}// 等同于function fn(args){ return spawn(function*() { // .... 所有await替换成yield let v1 = yield promise1(); let v2 = yield promise2(v1); ... }); }
es6 提供了Generator 生成函数: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*
function *gen(){ let i=0, v; while(i<2) v=yield i++; return i;}let g = gen();g.next(); //{value:0, done:false}g.next(); //{value:1, done:false}g.next(); //{value:2, done:true}
编译器会把generator function拆成多个断点(用switch/case,不同断点执行不同case语句),并把当前断点位置存在对象g上以便下次继续:
//上文function *gen(){..}代码被babel编译成了带switch/case普通函数:var _marked = /*#__PURE__*/regeneratorRuntime.mark(gen);function gen() { var i, v; return regeneratorRuntime.wrap(function gen$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: i = 0, v = void 0; case 1: if (!(i < 2)) { _context.next = 7; break; } _context.next = 4; return i++; case 4: v = _context.sent; _context.next = 1; break; case 7: return _context.abrupt("return", i); case 8: case "end": return _context.stop(); } } }, _marked, this);}var g = gen();g.next(); //{value:0, done:false}g.next(); //{value:1, done:false}g.next(); //{value:2, done:true}
前面提到async/await 其实被编译成了 generator函数(function*(){...})):
async function fn(args){ let v1 = await promise1(); let v2 = await promise2(v1); ...}// 等同于function fn(args){ return spawn(function*() { // .... 所有await替换成yield let v1 = yield promise1(); let v2 = yield promise2(v1); ... }); }
思考下如何写出spawn函数?
function spawn(generator){ let g = generator(); // create a generator(object) to run return new Promise((final_resolve, final_reject) => { // inner closure function with final_resolve/final_reject function feed_and_take_one(last_async_value, isError) { let this_yield = isError ? g.throw(last_async_value) : g.next(last_async_value); //feed last_async_value back to last yield point let promise = this_yield.value; if (this_yield.done) final_resolve(promise); else promise.then(feed_and_take_one, e=>feed_and_take_one(e, true)) } feed_and_take_one(); });}
思路:
测试spawn函数:
// manual testing:function pause_and_return(n){ return new Promise(resolve =>{ console.log(`waiting ${n} seconds...`); setTimeout(()=>resolve(n), n*1000); });}function throw_async_error(msg){ return new Promise((_, reject)=> reject(new Error(msg)));}// async/await version:async function async_routine(){ try { for (let i of [3, 2, 1]) { let a = await pause_and_return(i); console.log(`-----yield async_value: ${a}`); } await throw_async_error("my error"); await pause_and_return(100); //shouldn't run this after throw }catch(e){ console.log(`------catch error: ${e}`); return pause_and_return(6); }}async_routine().then(v => console.log(v));console.log("====== non blocking outside async");// spawn version: async by generatorfunction *coroutine(){ try { for (let i of [3, 2, 1]) { let a = yield pause_and_return(i); console.log(`-----yield async_value: ${a}`); } yield throw_async_error("my error"); yield pause_and_return(100); //shouldn't run this after throw }catch(e){ console.log(`------catch error: ${e}`); return pause_and_return(6); }}spawn(coroutine).then(v => console.log(`return final async value: ${v}`));