ES7有一个特性,便是async
/ await
。以下便是个例子
async function a1() {
return new Promise((resolve, reject) => {
setTimeout(resolve, 1000);
})
}
async function a2() {
await a1();
console.log("2333");
}
async
/ await
,就我所知比较早的实现出于F#等函数式语言,后被C# 4.0所吸收。套用MSDN的流程图:
其可以有效地梳理流程,避免过多的语法噪音(说你呢Promise
)。不过当然了,其只适合命令式的调用,适合过程表达。要梳理流程的话,还是Promise
的then
链式调用更为合适——利用函数的组合来把整个流程表达清楚。另外,await
/ async
会不可避免地用到try..catch
,而try..catch
内的代码是不会被JavaScript引擎所优化的。所以在这时用Promise
的reject
来代为处理也比较合适。
先来一段 callback hell
测试代码:
console.log("1");
setTimeout(() => {
console.log("2");
setTimeout(() => {
console.log("3");
setTimeout(() => {
console.log("4");
}, 3000)
}, 2000);
}, 1000);
在我的记忆中,Wind.js是前端领域中最早利用async
/ await
梳理流程的库。如上面这段代码,我们可以等价成(假设a1 == setTimeout
)以下代码。
var A = Wind.compile("async", function () {
console.log("1");
$await(a1(1000));
console.log("2");
$await(a1(2000));
console.log("3");
$await(a1(3000));
console.log("4");
return 5;
});
其将被展开为以下代码:
* async << function () { */ (function () {
var _builder_$0 = Wind.builders["async"];
return _builder_$0.Start(this,
_builder_$0.Delay(function () {
/* console.log("1"); */ console.log("1");
/* $await(a1(1000)); */ return _builder_$0.Bind(a1(1000), function () {
/* console.log("2"); */ console.log("2");
/* $await(a1(2000)); */ return _builder_$0.Bind(a1(2000), function () {
/* console.log("3"); */ console.log("3");
/* $await(a1(3000)); */ return _builder_$0.Bind(a1(3000), function () {
/* console.log("4"); */ console.log("4");
return _builder_$0.Return(5);
});
});
});
})
);
/* } */ })
//@ sourceURL=wind/1_anonymous.js
作为一个较早的异步流程库,其缺陷有几:
原作者所说的
JIT
是在前端进行然后eval
的,导致其代码不会被JavaScript引擎优化。(当然,预编译兴许能解决此问题)语法噪音过多。
try .. catch
的大量使用(js引擎不优化try..catch
内代码块)有造成函数栈溢出的可能
翻其代码。其Wind.builders.async.Start
函数内创建了一个Task
。该Task
创建时的delegate
将调用被.Delay
包装的函数。
Delay: function (generator) {
return {
next: function (_this, callback) {
try {
var step = generator.call(_this);
step.next(_this, callback);
} catch (ex) {
callback("throw", ex);
}
}
};
},
该函数的同步部分执行完毕后,返回另一个被.Bind
包装的函数。其亦拥有一个next
方法。
Bind: function (task, generator) {
return {
next: function (_this, callback) {
var onComplete = function () {
if (this.error) {
callback("throw", this.error);
} else {
var nextTask;
try {
nextTask = generator.call(_this, this.result);
} catch (ex) {
callback("throw", ex);
return;
}
nextTask.next(_this, callback);
}
}
if (task.status == "ready") {
task.addEventListener("complete", onComplete);
task.start();
} else if (task.status == "running") {
task.addEventListener("complete", onComplete);
} else {
onComplete.call(task);
}
}
};
}
在Wind内建的事件机制的帮助下,该方法将在当前task完成后运行。等其运行完毕,得到下一个被包装的函数并执行。一直到最后调用了Return.next
,callback
回去,流程结束。
这个思想,似乎就是generator
的思想。
我们看看现代的transformer吧。
async function ping() {
for (var i = 0; i < 10; i++) {
await delay(300);
console.log("ping");
}
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
先看看会转到es5的babel是怎么实现async
/ await
的。
function ping() {
var i;
return regeneratorRuntime.async(function ping$(context$1$0) {
while (1) switch (context$1$0.prev = context$1$0.next) {
case 0:
i = 0;
case 1:
if (!(i < 10)) {
context$1$0.next = 8;
break;
}
context$1$0.next = 4;
return regeneratorRuntime.awrap(delay(300));
case 4:
console.log("ping");
case 5:
i++;
context$1$0.next = 1;
break;
case 8:
case "end":
return context$1$0.stop();
}
}, null, this);
}
function delay(ms) {
return new Promise(function (resolve) {
return setTimeout(resolve, ms);
});
}
通过这样的代码,我们发现以下事实:babel维护了一个状态机,然后通过regeneratorRuntime
来运行代码。
看看TypeScript Compiler的实现(to ES6):
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator.throw(value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments)).next());
});
};
function ping() {
return __awaiter(this, void 0, void 0, function* () {
for (var i = 0; i < 10; i++) {
yield delay(300);
console.log("ping");
}
});
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
regeneratorRuntime
是generator
的运行时实现之一,而__awaiter也是generator
的包装。当然,后者的generator
是使用引擎的,前者的是用js模拟的。根据MSDN博客,他们表示,要让tsc支持async
-> es5的话,也要实现一个基于状态机的generator
。
最后看看Chakra
引擎内建的实现。可以看到的是,其await
实现与yield
实现类似。yield和await在token解析时大多相邻甚至写在一起,最后到下面那个函数都以类型Js::OpCode::Yield进行处理。所以整块Chakra中,出现await的基本只有Parser部分。比如ByteCodeEmitter::Emit
内:
case knopAwait:
case knopYield:
byteCodeGenerator->StartStatement(pnode);
funcInfo->AcquireLoc(pnode);
Emit(pnode->sxUni.pnode1, byteCodeGenerator, funcInfo, false);
EmitYield(pnode->sxUni.pnode1->location, pnode->location, byteCodeGenerator, funcInfo);
funcInfo->ReleaseLoc(pnode->sxUni.pnode1);
byteCodeGenerator->EndStatement(pnode);
break;
await
直接被当做yield
执行(当然到这里的代码已经经过了其他处理)。
嘛,总之await
/ async
就是层generator
的语法糖嘛。看看规范是怎么脱糖的:
function spawn(genF, self) {
return new Promise(function(resolve, reject) {
var gen = genF.call(self);
function step(nextF) {
var next;
try {
next = nextF();
} catch(e) {
// finished with failure, reject the promise
reject(e);
return;
}
if(next.done) {
// finished with success, resolve the promise
resolve(next.value);
return;
}
// not finished, chain off the yielded promise and `step` again
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
这糖挺甜的,不是吗(笑
又水了一篇没意义的文章呢,明明翻个规范什么都解决了。总结:
async / await === Promise + generator + yield
参考代码:
https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-async-to-generator
https://github.com/babel/babel/tree/master/packages/babel-regenerator-runtime/
参考文章: