“async/await”是一种与“promise”协作的特殊的语法。它使得异步工作更加容易理解和使用。
Async函数
我们从async关键词开始,它可以被放置在任何函数的开头位置,比如:
async function f() {
return 1;
}
这里的async表示:该函数将始终返回一个promise。即使您的代码没有显式返回一个promise,在JavaScript运行时也会自动包装一个promise,用于返回指定的值。
在这个例子中,这段代码将会返回一个result为1的promise:
async function f() {
return 1;
}
f().then(alert); // 1
当然,我们也可以显式的返回一个promise:
async function f() {
return Promise.resolve(1);
}
f().then(alert); // 1
async确保了函数会返回一个promise。挺简单的对吧?接下来,是另一个关键词await,仅仅能在async标记的函数中生效。
Await
语法说明:
//该段代码仅仅能在 async 标记的函数中生效
let value = await promise;
关键词await确保JavaScript运行时将会等待promise执行完毕并返回结果。
下面是一段使用promise并在一秒后返回结果的例子:
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 1000)
});
let result = await promise; // 等待至promise获得结果 (*)
alert(result); // "done!"
}
f();
该函数在运行至await时,执行了“pauses”操作,直至promise执行完毕后重新执行接下来的代码。所以该段代码将在一秒后显示“done!”。
让我们强调一遍:await将顺序使得JavaScript的运行时等待promise执行完毕,而后继续运行余下代码。等待时的操作并不会消耗任何CPU资源,因为此时的运行时可以同时执行其他操作:执行其他的代码,处理事件逻辑等。
这仅仅是一项相对promise.than更为优雅的语法来获取promise的运行结果,更容易阅读和编写代码而已。
不能将await用于任何标准函数
如果您尝试将await运行在任何未标记为async的函数中,都会产生一个语法错误
function f() {
let promise = Promise.resolve(1);
let result = await promise; // Syntax error
}
我们如果没有使用async标记函数,那么我们就会得到这个语法错误。换句话说,await仅可以运行在async function中。
让我们修改 Promises chaining 中的例子,使用async/await来重写这个例子。
- 我们需要将.then替换为await。
- 我们需要将函数修改为async function。
async function showAvatar() {
// read our JSON
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
// read github user
let githubResponse = await fetch(`https://api.github.com/users/${user.name}`);
let githubUser = await githubResponse.json();
// show the avatar
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
// wait 3 seconds
await new Promise((resolve, reject) => setTimeout(resolve, 3000));
img.remove();
return githubUser;
}
showAvatar();
相当容易阅读和理解对吧。
await 并不能在顶层环境中生效
人们在开始使用await时,总是容易忘记必须在async function内部使用。比如以下代码将会报错:
// syntax error in top-level code
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
所以我们需要声明一个async function来包裹该段代码。
await 可以接受 thenables
类似promise.then,await准许使用then方法。需要申明的是,这里指的是一个非promise对象,但它持有.then方法,那么就可以配合await使用。
比如以下例子,await接受new Thenable(1):
class Thenable {
constructor(num) {
this.num = num;
}
then(resolve, reject) {
alert(resolve); // function() { native code }
// resolve with this.num*2 after 1000ms
setTimeout(() => resolve(this.num * 2), 1000); // (*)
}
};
async function f() {
// waits for 1 second, then result becomes 2
let result = await new Thenable(1);
alert(result);
}
f();
如果await与一个含有.then方法的非promise对象组合,同时其支持resolve、reject两个方法作为参数。那么await将会等待其中之一的函数被调用(注释(*)所在行)并在之后继续运行剩余代码。
Async方法
类函数亦可以被定义为异步函数,仅需将async置于函数声明前。
类似于:
class Waiter {
async wait() {
return await Promise.resolve(1);
}
}
new Waiter()
.wait()
.then(alert); // 1
这和之前的其他代码端是一样的作用:应用await返回一个promise对象。
错误处理
如果promise顺利完成,那么await promise将返回一个值。但假如触发了错误,那么将在此行代码中throw一个错误。
以下代码:
async function f() {
await Promise.reject(new Error("Whoops!"));
}
等同于:
async function f() {
throw new Error("Whoops!");
}
在真实的运行环境中,可能需要消耗一些时间来触发错误。所以await将会进行等待,而之后抛出一个错误。我们可以通过try…catch来捕获错误,同样的方法也适用于throw:
async function f() {
try {
let response = await fetch('http://no-such-url');
} catch(err) {
alert(err); // TypeError: failed to fetch
}
}
f();
触发了错误之后,运行代码将跳转至catch代码块。我们可以使用如下代码:
async function f() {
try {
let response = await fetch('/no-user-here');
let user = await response.json();
} catch(err) {
// catches errors both in fetch and response.json
alert(err);
}
}
f();
但如果我们没有使用try…catch,那么将会在异步调用f()时触发rejected。我们也可以添加.catch来处理错误:
async function f() {
let response = await fetch('http://no-such-url');
}
// f() becomes a rejected promise
f().catch(alert); // TypeError: failed to fetch // (*)
如果我们忘记添加.catch,我们将获得一个未被捕获的错误。我们也可以使用一个全局事件捕获方法来处理,参见Promise chaining。
async/await 和 promise.then/catch
当我们使用async/await时,我们仅仅需要.then,因为await会接受正确结果。我们可以使用try…catch来取代.catch。同时这也将更为便利。
但处于顶层代码逻辑时,我们的逻辑代码处在async function以外,我们并不能直接使用await,所以添加.then/catch代码块来获取最终结果是更为普遍的做法。
async/await与Promise.all相互合作
当我们需要等待多个promises时,我们可以使用Promise.all之后使用await:
// wait for the array of results
let results = await Promise.all([
fetch(url1),
fetch(url2),
...
]);
假如触发了一个错误,它也会和其他promise一样工作:从运行失败的Promise.all节点中断,之后我们可以通过try…catch来捕获错误。
总结
async定以后的函数有两层作用:
- 确保它总是返回一个promise。
- 允许在函数内部使用await。
await关键词确保js运行时将会等待promise处理完毕,并且:
- 如果触发了一个运行错误,promise运行中断,并在改处类似触发throw error。
- 此外,它将返回一个结果,所以我们可以将其赋值给一个变量。
我们一起提供了一个伟大的框架来更为简便的完成异步操作。
有了async/await的帮组,我们可以大幅减少使用promise.then/catch,但我们依然不应该忘记这些技术是基于promises,很可能我们会不得不继续使用promise的方法。同时,Promise.all相当适合等待多个任务顺序执行的操作。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。