「async/await」是 promises 的另外一种更便捷更流行的写法,同时它也更易于理解和使用。html
让咱们以 async
这个关键字开始。它能够被放置在任何函数前面,像下面这样:git
async function f() { return 1; }
在函数前面的「async」这个单词表达了一个简单的事情:即这个函数老是返回一个 promise。即便这个函数在语法上返回了一个非 promise 的值,加了「async」这个关键字就会指示 JavaScript 引擎自动将返回值包装成一个解析后的 promise。github
例如,如下的代码就返回了一个以 1
为结果的解析后的 promise, 让咱们试一下:json
async function f() { return 1; } f().then(alert); // 1
... 咱们也能够显式返回一个 promise,结果是同样的:api
async function f() { return Promise.resolve(1); } f().then(alert); // 1
因此说,async
确保了函数的返回值是一个 promise,也会包装非 promise 的值。很简单是吧?可是还没完。还有一个关键字叫 await
,它只在 async
函数中有效,也很是酷。promise
语法以下:app
// 只在 async 函数中有效 let value = await promise;
关键字 await
让 JavaScript 引擎等待直到 promise 完成并返回结果。异步
这里的例子就是一个 1 秒后解析的 promise:async
async function f() { let promise = new Promise((resolve, reject) => { setTimeout(() => resolve("done!"), 1000) }); let result = await promise; // 等待直到 promise 解析 (*) alert(result); // "done!" } f();
这个函数在执行的时候,「暂停」在了 (*)
那一行,而且当 promise 完成后,拿到 result
做为结果继续往下执行。因此「done!」是在一秒后显示的。函数
划重点:await
字面的意思就是让 JavaScript 引擎等待直到 promise 状态完成,而后以完成的结果继续执行。这个行为不会耗费 CPU 资源,由于引擎能够同时处理其余任务:执行其余脚本,处理事件等。
相比 promise.then
来获取 promise 结果,这只是一个更优雅的语法,同时也更易书写。
不能在普通函数中使用 await
若是咱们尝试在非 async 函数中使用 await
的话,就会报语法错误:
function f() { let promise = Promise.resolve(1); let result = await promise; // 语法错误 }
若是函数前面没有 async
关键字,咱们就会获得一个语法错误。就像前面说的,await
只在 async 函数
中有效。
让咱们拿 Promises 链那一章的 showAvatar()
例子改写成 async/await
的形式:
await
替换掉 .then
的调用async
关键字async function showAvatar() { // 读取 JSON let response = await fetch('/article/promise-chaining/user.json'); let user = await response.json(); // 读取 github 用户信息 let githubResponse = await fetch(`https://api.github.com/users/${user.name}`); let githubUser = await githubResponse.json(); // 显示头像 let img = document.createElement('img'); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.append(img); // 等待 3 秒 await new Promise((resolve, reject) => setTimeout(resolve, 3000)); img.remove(); return githubUser; } showAvatar();
简洁明了,是吧?比以前可强多了。
await
不能在顶层代码运行
刚开始使用 await
的人经常会忘记 await
不能用在顶层代码中。如,下面这样就不行:
// 用在顶层代码中会报语法错误 let response = await fetch('/article/promise-chaining/user.json'); let user = await response.json();
咱们能够将其包裹在一个匿名 async 函数中,如:
(async () => { let response = await fetch('/article/promise-chaining/user.json'); let user = await response.json(); ... })();
await
能够接收「thenables」
像 promise.then
那样,await
被容许接收 thenable 对象(具备 then
方法的对象)。有些对象虽然不是 promise,可是却兼容 promise,若是这些对象支持 .then
,那么就能够对它们使用 await
。
下面是一个 Thenable
类,await
接收了该类的实例:
class Thenable { constructor(num) { this.num = num; } then(resolve, reject) { alert(resolve); // 1 秒后解析为 this.num*2 setTimeout(() => resolve(this.num * 2), 1000); // (*) } }; async function f() { // 等待 1 秒, result 变为 2 let result = await new Thenable(1); alert(result); } f();
若是 await
接收了一个非 promise 的可是提供了 .then
方法的对象,它就会调用这个 then 方法,并将原生函数 resolve
,reject
做为参数传入。而后 await
等到这两个方法中的某个被调用(在例子中发生在(*)的那一行),再处理获得的结果。
Async methods
若是想定义一个 async 的类方法,在方法前面添加 async
就能够了:
class Waiter { async wait() { return await Promise.resolve(1); } } new Waiter() .wait() .then(alert); // 1
若是一个 promise 正常解析,await promise
返回的就是其结果。可是若是 promise 被拒绝,就会抛出一个错误,就像在那一行有个 throw
语句那样。
这里的代码:
async function f() { await Promise.reject(new Error("Whoops!")); }
...和下面是同样的:
async function f() { throw new Error("Whoops!"); }
在真实的环境下,promise 被拒绝前一般会等待一段时间。因此 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
块中。固然也能够用 try 包裹多行 await 代码:
async function f() { try { let response = await fetch('/no-user-here'); let user = await response.json(); } catch(err) { // 捕获到 fetch 和 response.json 中的错误 alert(err); } } f();
若是咱们不使用 try...catch
,由f()
产生的 promise 就会被拒绝。咱们能够在函数调用后添加 .catch
来处理错误:
async function f() { let response = await fetch('http://no-such-url'); } // f() 变为一个被拒绝的 promise f().catch(alert); // TypeError: failed to fetch // (*)
若是咱们忘了添加 .catch
,咱们就会获得一个未处理的 promise 错误(显示在控制台)。咱们能够经过在错误处理与 Promise 章节讲的全局事件处理器来捕获这些。
async/await
和 promise.then/catch
当咱们使用 async/await
时,几乎就不会用到 .then
了,由于为咱们await
处理了异步等待。而且咱们能够用 try...catch
来替代 .catch
。这一般更加方便(固然不是绝对的)。
可是当咱们在顶层代码,外面并无任何 async
函数,咱们在语法上就不能使用 await
了,因此这时候就能够用 .then/catch
来处理结果和异常。
就像上面代码的 (*)
那行同样。
async/await
能够和 Promise.all
一块儿使用
当咱们须要同时等待多个 promise 时,咱们能够用 Promise.all
来包裹他们,而后使用 await
:
// 等待多个 promise 结果 let results = await Promise.all([ fetch(url1), fetch(url2), ... ]);
若是发生错误,也会正常传递:先从失败的 promise 传到 Promise.all
,而后变成咱们能用 try...catch
处理的异常。
咱们在微任务和事件循环章节讲过,promise 回调是异步执行的。每一个 .then/catch/finally
回调首先被放入「微任务队列」而后在当前代码执行完成后被执行。
Async/await
是基于 promise 的,因此它内部使用相同的微任务队列,而且相对宏任务来讲具备更高的优先级。
例如,看代码:
setTimeout(handler, 0)
,应该以零延迟运行 handler
函数。let x = await f()
,函数 f()
是异步的,可是会当即运行。那么若是 await
在 setTimeout
下面,哪个先执行呢?
async function f() { return 1; } (async () => { setTimeout(() => alert('timeout'), 0); await f(); alert('await'); })();
这里很肯定:await
老是先完成,由于(做为微任务)它相比 setTimeout
具备更高的优先级。
函数前面的关键字 async
有两个做用:
await
这个 await
关键字又让 JavaScript 引擎等待直到 promise 完成,而后:
throw error
语句同样。这两个关键字一块儿用就提供了一个很棒的方式来控制异步代码,而且易于读写。
有了 async/await
咱们就几乎不须要使用 promise.then/catch
,可是不要忘了它们是基于 promise 的,因此在有些时候(如在最外层代码)咱们就能够用 promise 的形式。再有就是 Promise.all
能够帮助咱们同时处理多个异步任务。