理解 JavaScript 的 async/await
ECMAScript 6 入门 - async 函数
ECMAScript 6 入门 - Generator 函数的异步应用
使用回调函数的传统异步编程有可能嵌套多层回调函数,当嵌套真的多到一定程度时,视觉效果和维护难度都是地狱级的灾难,这也就是为什么会把多层回调嵌套叫做回调地狱。多个异步操作形成强耦合,看着不知所云,改起来头晕目眩,是人都要崩溃了。
fetch(urlA, function (data) {
fetch(urlB, function (data) {
// ... 不停地在回调函数中再写新的回调函数
});
});
将回调函数的嵌套改成链式调用写法
new Promise((resolve) => {
resolve(fetch(url));
}).then((data) => {
return data;
}).then((data) => {
// … 不停地继续链式调用
});
链式调用比回调嵌套可读性更高,比较符合阅读逻辑:从上到下,异步取到一个结果,再执行调用链的下一个操作。
但是,假如链中有一个函数需要被另一个异步操作复用呢,例如这样:
new Promise((resolve) => {
resolve(fetch(url));
}).then((data) => {
reuseCode(); // 需复用的代码
});
在一个 Promise 链中要插入一个几乎毫不相关的异步操作是很奇怪的,体会一下:
new Promise((resolve) => {
resolve(fetch(urlA)); // 异步操作 A
}).then((data) => {
return (data, fetch(urlB)); // 插入异步操作 B
}).then((dataA, dataB) => {
dataA ? reuseCode(dataA) : reuseCode(dataB); // 需复用的代码
});
新加入的这一层调用链,进行了异步操作 B ,并将结果向下一级以供复用代码使用,除此之外,还要原封不动地把异步操作 A 的结果往下传,不仅代码比较繁杂,逻辑上也很奇怪。就好像好好的一个家,莫名其妙突然来了个流浪汉,除了白用我家的锅碗瓢盆之外并不能给家庭做出什么贡献,多么别扭。
这样一来,原家庭成员们不可避免地会有这样一个想法:这个流浪汉倒也不是多讨厌,给他点吃的还是可以的,只要他老老实实呆在家门外面,别来打搅我们的生活就好了。
这种情况放在代码里,就是用同步的写法来写异步,没有整体性过强的 then 调用链的存在,代码自然会灵活很多。要达到这个目的,可以用 async/await 来实现。
async/await 最大的特点就是将异步逻辑用同步写法实现。使用 async/await 改造上文例子:
async doSomething {
let dataA = await fetch(urlA)); // 异步操作 A
let dataB = await fetch(urlB); // 异步操作 B
dataA ? reuseCode(dataA) : reuseCode(dataB); // 需复用的代码
}
doSomething();
取消调用链的写法,改用同步写法,逻辑更清晰了。
async 函数实际上是 Generator 函数的语法糖。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
function* gen(){
var result = yield fetch(url); // 异步操作写在 Generator 中
}
var g = gen(); // 获取遍历器对象
var result = g.next(); // 使遍历器执行,获取 fetch 到的 Promise 对象
result.value.then(function(data){ // 取到 Promise 对象后还是用链式调用写法
return data.json();
}).then(function(data){
g.next(data);
}).then(function(data){
……
});
虽然 Generator 函数将异步操作表示得很简洁,但是流程管理却不方便,需要在链式调用中不停地用 next 使遍历器执行。
ECMAScript 6 入门 - async 函数
async 函数对 Generator 函数的改进,体现在以下四点。
- async 函数内置执行器,会自动执行,输出最后结果。
这完全不像 Generator 函数,需要调用 next 方法,或者用 co 模块,才能真正执行,得到最后结果。- 更好的语义。async和await,比起星号和yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。
- 更广的适用性。
co 模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。- 返回值是 Promise。
async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。