新的async
和await
关键字——使异步代码更简洁、更易懂、更易维护——已在 Firefox 52 中推出。目前可在最新的开发者版中使用,Firefox 52 计划于 2017 年 3 月正式发布。
JavaScript 在 Web 上出色的单线程性能和响应能力归功于其普遍的异步设计。不幸的是,同样的设计也导致了“回调地狱”,其中对异步函数的顺序调用需要深度嵌套、难以管理的代码,如下面的使用localforage库的稍微人为设计的示例所示
function foo(callback) {
localforage.setItem('x', Math.random(), function(err) {
if (err) {
console.error("Something went wrong:", err);
} else {
localforage.getItem('x', function(err, value) {
if (err) {
console.error("Something went wrong:", err);
} else {
console.log("The random number is:", value);
}
if (callback) {
callback();
}
});
}
});
}
foo(function() { console.log("Done!"); });
如果您跳过了这段代码,或者没有立即理解它的作用,那就是问题所在。
ES2015 开始通过将Promise标准化为链接的异步函数来解决此问题。自从引入以来,Promise 已成为新的 Web 标准(包括fetch和服务工作线程)不可或缺的一部分。它们使得可以将前面的示例重写为
function foo() {
return localforage.setItem('x', Math.random())
.then(() => localforage.getItem('x'))
.then((value) => console.log("The random number is:", value))
.catch((err) => console.error("Something went wrong:", err));
}
foo().then(() => console.log("Done!"));
由于有了 Promise,代码不会随着每次连续调用而嵌套得更深,并且所有错误处理都可以在链的末尾合并到一个案例中。
请注意,在上面的示例中,foo()
在localforage 完成其工作之前立即返回。因为foo()
本身返回一个 Promise,所以可以在它使用.then()
方法完成之后安排未来的回调。
从语义上讲,上面的示例更加简单明了,但在语法上,仍然有很多内容需要阅读和理解。新的async
和await
关键字是语法糖,建立在 Promise 之上,有助于使 Promise 更易于管理
async function foo() {
try {
await localforage.setItem('x', Math.random());
let value = await localforage.getItem('x');
console.log("The random number is:", value);
} catch (err) {
console.error("Something went wrong:", err);
}
}
foo().then(() => console.log("Done!"));
上面的代码在功能上与前面的示例相同,但更容易理解和维护,因为函数体现在类似于常见的同步函数。
标记为async
的函数始终返回 Promise,因此对.then()
的调用在其返回值上工作以安排回调。以await
为前缀的表达式有效地暂停函数,直到表达式解析。如果await
ed 表达式遇到错误,则执行传递到catch
块。如果未捕获,则返回的 Promise 将进入拒绝状态。
类似地,而不是在async
函数内部处理错误,也可以在返回值上使用普通的.catch()
方法
async function foo() {
await localforage.setItem('x', Math.random());
let value = await localforage.getItem('x');
console.log("The random number is:", value);
}
foo().catch(err => console.error("Something went wrong:", err))
.then(() => console.log("Done!"));
对于更实际的示例,请考虑您可能编写的用于取消用户Web 推送通知订阅的函数
function unsubscribe() {
return navigator.serviceWorker.ready
.then(reg => reg.pushManager.getSubscription())
.then(subscription => subscription.unsubscribe())
.then(success => {
if (!success) {
throw "unsubscribe not successful";
}
});
}
使用async
和await
,它变成
async function unsubscribe() {
let reg = await navigator.serviceWorker.ready;
let subscription = await reg.pushManager.getSubscription();
let success = await subscription.unsubscribe();
if (!success) {
throw "unsubscribe not successful";
}
}
两者功能相同,但后一个示例隐藏了 Promise 的复杂性,并将异步代码转换为像同步代码一样读取(和执行)的代码:从上到下,等待每行代码完全解析,然后才能转到下一行。
对async
和await
关键字的原生跨浏览器支持仍然处于起步阶段,但您可以借助 JavaScript 转译器(如Babel)在今天使用它们,该转译器可以将async
/ await
转换为功能上等效的向后兼容代码。
要详细了解async
和await
关键字或 Promise,请查看以下资源
请记住,async
和await
只是 Promise 的帮助程序:您可以混合和匹配任何语法,并且您了解的所有关于 Promise 的知识都直接适用于async
和await
。
特别感谢Jamund Ferguson建议改进本文中代码示例。
关于 Dan Callahan
Mozilla 开发者关系工程师,前 Mozilla Persona 开发人员。
10 条评论