## async函數
### 含義
ES2017標準引入了 `async` 函數,用一句話解釋,它就是 `Generator` 函數的語法糖。
先來看看 `Generator` 函數如何依次讀取兩個文件
```js
let fs = require('fs')
let readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(filename, function (error, data) {
if (error) return reject(error)
resolve(data)
})
})
}
let gen = function* () {
let f1 = yield readFile('/etc/fstab')
let f2 = yield readFile('/etc/shells')
console.log(f1.toString())
console.log(f2.toString())
}
```
將上面的代碼寫成 `async` 的形式:
```js
let asyncReadFile = async function () {
let f1 = await readFile('/etc/fstab')
let f2 = await readFile('/etc/shells')
console.log(f1.toString())
console.log(f2.toString())
}
```
`async` 函數對 `Generator` 函數的改進體現在以下4點:
- 內置執行器:`Generator` 函數的執行必須依靠執行器,所以才有 `co` 模塊,而 `async` 函數自帶執行器
- 更好的語義:`async` 和 `await` 比星號和 `yield` 更加語義化。
- 更廣的適用性
- 返回值是`Promise`
### 用法
`async` 函數返回一個 `Promise` 對象,可以使用 `then` 方法添加回調函數,當函數執行時,一旦遇到 `await` 就會先返回,等到異步操作完成,再接著執行函數體內后面的語句
```js
async function getStockPriceByName (name) {
let symbol = await getStockSymbol(name)
let stockPrice = await getStockPrice(symbol)
return stockPrice
}
getStockPriceByName('goog').then(function (result) {
console.log(result)
})
```
### 語法
#### 返回Promise對象
`async` 函數返回一個 `Promise` 對象。`async` 函數內部 `return` 語句返回的值,會成為 `then` 方法回調函數的參數
```js
async function f () {
return 'hello world'
}
f().then(v => console.log(v))
// 'hello world'
```
`async` 函數內部拋出的錯誤會導致返回的 `Promise` 對象變為 `reject` 狀態,拋出的錯誤對象會被 `catch` 方法捕獲
```js
async function f () {
throw new Error('some is wrong')
}
f().then(
v => console.log(v),
e => console.log(e)
)
// Error: some is wrong
```
#### Promise對象的狀態變化
`async` 函數返回的 `Promise` 對象必須等到內部所有的 `await` 命令后面的 `Promise` 對象執行完才會發生狀態變化,除非遇到 `return` 語句或拋出錯誤。也就是只有 `async` 函數內部的異步操作執行完,才會執行 `then` 方法指定的回調函數。
#### await命令
正常情況下,`await` 命令后面是一個 `Promise` 對象,如果不是,會被轉化成一個立即 `resolve` 的 `Promise` 對象
#### 錯誤處理
如果 `await` 后面的異步操作出錯,那么等同于 `async` 函數返回的 `Promise` 對象被 `reject`
```js
async function f () {
await new Promise (function (resolve, reject) {
throw new Error('出錯了')
})
}
f().then(v => console.log(v)).catch(e => console.log(e))
// Error:出錯了
```
防止這種情況的方法就是將其放在 `try...catch`當中
```js
async function f () {
try {
await new Promise(function (resolve, reject) {
throw new Error('出錯了')
})
} catch (e) {}
return await('hello world')
}
```
#### 注意點
1. `await` 命令后面的 `Promise` 對象的運行結果可能是 `rejected`,所以最好把 `await` 命令放在 `try...catch`當中
```js
async function myFunction () {
try {
await somethingThatReturnAPromise()
} catch (err) {
console.log(err)
}
}
```
2. 多個 `await` 命令后面的異步操作如果不存在繼發關系,最好讓他們同時觸發
```js
// bad 比較耗時
let foo = await getFoo()
let bar = await getBar()
// good
let [foo, bar] = await Promise.all([getFoo(), getBar()])
```
3. `await` 命令只能在 `async` 函數之中,如果用在普通函數中就會報錯。
### async函數的實現原理
`async` 函數的實現原理就是將 `Generator` 函數和自動執行器包裝在一個函數里。