async/await: 最简洁地写串行的异步操
我们知道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/await只是语法糖
注意async function依然是非阻塞的异步执行,async/await只是语法糖,也就是相当于编译器替我们写了回调函数!
理解 async/await 背后的 Generator生成函数
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}
- 对比: 函数与生成函数
- 函数的调用方式: func(): 从头执行到return -> 返回给caller
- 生成函数的调用方式: 多次g.next(init_value): 每次从上一次的yield位置(同时将参数init_value传入此位置)执行到下一个yield -> 返回给caller
- yield 相当于一个断点,跳转到caller, 当caller再执行g.next()时再从此断点继续
思考题:如何用Generator实现 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);
...
});
}
思考下如何写出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();
});
}
思路:
- caller创建一个g=gen()对象,执行g.next() 得到一个yield出来的promise
- promise中得到结果resolve_value后, 再次执行g.next(resolve_value)传回生成函数,等待返回下一个promise
- 也就是在promise的回调中递归调用步骤2
- 步骤2中yield value为done状态时,停止(递归)
测试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 generator
function *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}`));
网友评论