## 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` 函数和自动执行器包装在一个函数里。