# Async/await
有一種特殊的語法可用一種更舒適的方式使用 promise,稱為 "async/await"。
## Async 函數
我們從`async`關鍵字開始。它可以放在函數前,就像這樣:
~~~js
async function f() {
return 1;
}
~~~
> 函數前的 "async" 意味著一件簡單的事情:函數總是會返回 promise。如果代碼中有`return <non-promise>`,那么 JavaScript 就會自動將其封裝到一個帶有該值的 resolved promise 中。
例如,上述代碼中返回一個帶有結果`1`的 resolved promise,我們可以進行測試:
~~~js
async function f() {
return 1;
}
f().then(alert); // 1
~~~
我們可以顯式的返回一個 promise,結果相同:
~~~js
async function f() {
return Promise.resolve(1);
}
f().then(alert); // 1
~~~
因此,`async`確保函數返回一個 promise,并在其中封裝非 promise。很簡單對吧?但不僅僅如此。因為還有`await`關鍵字,它只在`async`函數中工作,而且非常酷。
## Await
語法:
~~~js
// 只在 async 函數中工作
let value = await promise;
~~~
`await`關鍵字使 JavaScript 等待,直到 promise 得到解決并返回其結果。
下面是一個 promise 在 1s 之后 resolve 的例子:
~~~js
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 1000)
});
let result = await promise; // 等待,直到 promise 執行 resolves (*)
alert(result); // “done!”
}
f();
~~~
函數在`(*)`行執行“暫停”,并在 promise 被處理時繼續執行,`result`變成其結果。上述代碼在一秒內顯示了 "done!"
> `await`字面上是讓 JavaScript 等待 promise 完成,然后繼續處理結果。這并不會消耗 CPU 資源,因為引擎可以同時處理其他任務:執行其他腳本,處理事件等。
這是一種比`promise.then`更優雅地獲取 promise 結果的語法,它更容易閱讀和編寫。
> 不能在常規函數中使用`await`" 如果我們嘗試在非 async 函數中使用 `await`,就會產生語法錯誤:
~~~js
function f() {
let promise = Promise.resolve(1);
let result = await promise; // 語法錯誤
}
~~~
如果我們忘記將`async`放在函數前,我們就會得到這樣的錯誤。如前面所說的,`await`只在`async 函數`中工作。
```js
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`,因為它會無效:
```js run
// 在頂層代碼中導致語法錯誤
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
```
所以我們需要將 await 代碼封裝在一個async 函數中。就像上述例子一樣。
`await`接受 thenables" 像 `promise.then`、`await` 允許使用 thenable 對象(那些具有可調用的 `then` 方法)。同樣,我們的想法是,第三方對象可能不是 promise,而是與 promise 兼容:如果它支持 `.then`,那么就可以和 `await` 一起使用。
例如,這里的`await`接受`new Thenable(1)`:
~~~js
class Thenable {
constructor(num) {
this.num = num;
}
then(resolve, reject) {
alert(resolve); // function() { native code }
// 在 1000 ms 后將 this.num*2 作為 resolve 值返回
setTimeout(() => resolve(this.num * 2), 1000); // (*)
}
};
async function f() {
// 等待 1 秒后,結果變成 2
let result = await new Thenable(1);
alert(result);
}
f();
~~~
如果`await`獲取了帶有`.then`的非 promise 對象,它就會調用提供`resolve`、`reject`作為參數的原生函數。`await`等待,直到其中一個被調用(在上述示例中,發生在`(*)`行),然后繼續處理結果。
## Async 方法
類方法也可以 async,只要把 `async` 放在類方法前即可。
就像這樣:
```js
class Waiter {
*!*
async wait() {
*/!*
return await Promise.resolve(1);
}
}
new Waiter()
.wait()
.then(alert); // 1
```
意義相同:它確保返回值是 promise,并使 `await` 可用。
## Error 處理
如果一個 promise 正常 reslove,那么`await promise`就會返回結果。但在 reject 情況下,它會拋出 error,就像該行上有`throw`語句一樣。
代碼:
~~~js
async function f() {
await Promise.reject(new Error("Whoops!"));
}
~~~
與此相同:
~~~js
async function f() {
throw new Error("Whoops!");
}
~~~
在實際情況中,promise 可能需要一段時間才會變成 reject。因此`await`會等待,然后拋出 error。
我們可以使用`try..catch`來捕獲這個 error,就像常規`throw`方法一樣:
~~~js
async function f() {
try {
let response = await fetch('http://no-such-url');
} catch(err) {
*!*
alert(err); // 類型錯誤:獲取失敗
*/!*
}
}
f();
~~~
出錯時,控制權會進入`catch`塊。我們也可以封裝多行:
~~~js
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`,那么 async 函數`f()`調用所產生的 promise 就會變為 reject 狀態。我們可以通過追加`.catch`來處理它:
~~~js
async function f() {
let response = await fetch('http://no-such-url');
}
// f() 變成一個 rejected 狀態的 promise
*!*
f().catch(alert); // 類型錯誤:未能獲取 // (*)
*/!*
~~~
如果忘記在那里添加`.catch`,那么我們就會得到一個未處理的 promise 錯誤(可以在控制臺中看到)。我們可以像 info:promise-chaining 章節所描述的那樣,使用一個全局事件處理器來捕獲這樣的 error。
```smart header="`async/await`和 `promise.then/catch`" 我們使用 `async/await` 時,幾乎不需要 `.then`,因為 `await` 為我們處理等待。我們也可以使用 `try..catch` 替代 `.catch`。但這通常(并不總是)更方便。
但是在代碼的頂層,當我們在`async`函數的外部時,我們在語法上是不能使用`await`的,所以通常添加`.then/catch`去處理最終結果或者 error。
與上述示例的`(*)`行一樣。
~~~
````smart header="`async/await` 可以很好的與 `Promise.all` 協同工作"
當我們需要等待多個 promise 時,我們可以將它們封裝進 `Promise.all` 然后 `await`:
```js
// wait for the array of results
let results = await Promise.all([
fetch(url1),
fetch(url2),
...
]);
~~~
產生 error 的情況下,它會像往常一樣傳輸:從失敗的 promise 到`Promise.all`,然后變成一個我們可以使用`try..catch`捕獲的異常。
## 總結
函數前的 `async` 關鍵字有兩個作用:
1. 總是返回 promise。
2. 允許在其中使用 `await`。
在 promise 之前的 `await` 關鍵字,使 JavaScript 等待 promise 被處理,然后:
1. 如果有 error,就會產生異常,就像在那個地方調用了 `throw error` 一樣。
2. 否則,就會返回值,我們可以給它分配一個值。
它們一起為編寫易于讀寫的異步代碼提供了一個很好的框架。
對于 `async/await`,我們很少需要編寫 `promise.then/catch`,但我們不應該忘記它們是基于 promise 的。因為有時(例如,在最外面的范圍)
- 內容介紹
- EcmaScript基礎
- 快速入門
- 常量與變量
- 字符串
- 函數的基本概念
- 條件判斷
- 數組
- 循環
- while循環
- for循環
- 函數基礎
- 對象
- 對象的方法
- 函數
- 變量作用域
- 箭頭函數
- 閉包
- 高階函數
- map/reduce
- filter
- sort
- Promise
- 基本對象
- Arguments 對象
- 剩余參數
- Map和Set
- Json基礎
- RegExp
- Date
- async
- callback
- promise基礎
- promise-api
- promise鏈
- async-await
- 項目實踐
- 標簽系統
- 遠程API請求
- 面向對象編程
- 創建對象
- 原型繼承
- 項目實踐
- Classes
- 構造函數
- extends
- static
- 項目實踐
- 模塊
- import
- export
- 項目實踐
- 第三方擴展庫
- immutable
- Vue快速入門
- 理解MVVM
- Vue中的MVVM模型
- Webpack+Vue快速入門
- 模板語法
- 計算屬性和偵聽器
- Class 與 Style 綁定
- 條件渲染
- 列表渲染
- 事件處理
- 表單輸入綁定
- 組件基礎
- 組件注冊
- Prop
- 自定義事件
- 插槽
- 混入
- 過濾器
- 項目實踐
- 標簽編輯
- iView
- iView快速入門
- 課程講座
- 環境配置
- 第3周 Javascript快速入門