ES6-async

含义

async函数可以让异步操作更方便,它是Generator函数的语法糖.

使用Generator函数读取两个文件的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const fs = require('fs');
function readFile(fileName){
return new Promise((resolve, reject) => {
fs.readFile(fileName, (err, data) => {
if (err) {
return reject(err)
}
resolve(data)
})
})
}
var gen = function* () {
var f1 = yield readFile(fileName1)
var f2 = yield readFile(fileName2)
console.log(f1.toString())
console.log(f2.toString())

}

使用async函数:

1
2
3
4
5
6
var asyncReadfile = async function () {
var f1 = await readFile(fileName1)
var f2 = await readFile(fileName2)
console.log(f1.toString())
console.log(f2.toString())
}

可以看出,aysnc函数就是把Generator函数的yield换成await,*换成async

Generator函数的执行要靠执行器,await函数自带执行器,它的执行与普通的函数一样.

async函数返回值是Promise

用法

async函数返回一个Promise,可以使用then方法添加回调函数,函数执行的时候一旦遇到await就会先返回,等异步操作完成,在执行后面的语句

一个例子,指定多少毫秒后输出一个值:

1
2
3
4
5
6
7
8
9
10
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms)
})
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value)
}
asyncPrint('hello world', 50)

语法

返回Promise

async函数返回一个Promise,会成为then方法调用的参数

1
2
3
4
async function f() {
return 'hello world'
}
f().then(res => console.log(res))

async函数内部抛出的错误会导致Promise对象变成reject状态,抛出的错误会被catch方法捕获

1
2
3
4
async function f() {
throw new Error('Error!')
}
f().catch(err => console.log(err))

await命令

正常来说,await命令跟随一个Promise,如果不是,就会被转成一个立即resolve的Promise

1
2
3
4
async function f() {
return await 123
}
f().then(res => console.log(res))

await命令后面的Promise如果变为reject状态,会被catch方法捕获

1
2
3
4
async function f() {
Promise.reject('Error')
}
f().catch(err => console.log(err))

只要一个await语句后面的Promise变成了reject,整个函数都会停止执行

如果想要函数继续执行,可以将await放在try…catch语句里面

1
2
3
4
5
6
7
8
async function f() {
try {
await Promise.reject(new Error('Error!'))
} catch (e) { }
return await Promise.resolve('Success!')
}
f().then(res => console.log(res))
// Success!

另一种方法是在await后面的Promise添加catch方法

1
2
3
4
5
6
7
async function f() {
await Promise.reject(new Error('Error!')).catch(err => { console.log(err) })
return await Promise.resolve('Success!')
}
f().then(res => console.log(res))
// Error: Error!
// Success

使用注意

最好把await命令放在try…catch中

如果两个await命令后面的异步操作不存在激发关系,最好让他们同时触发,例如:

1
2
let foo = await getFoo()
let bar = await getBar()

这两个异步操作是相互独立的,又被写成了继发关系,所以比较耗时.可以让它们同时触发

1
2
// 写法一
let [foo,bar]=await Promise.all([getFoo(),getBar()])
1
2
3
4
5
// 写法二
let fooPromise = getFoo()
let barPromise = getBar()
let foo = await fooPromise
let bar = await barPromise

await只能用在async函数中,如果用在普通函数内会报错,async函数中其他函数的回调函数也不能用await

对一个数组遍历进行异步操作,正确的写法是采用for循环,而不是forEach

1
2
3
4
5
6
7
// 正确的写法
async function dbFun() {
let docs = [{}, {}, {}]
for (let doc of docs) {
await db.post(doc)
}
}

如果需要多个请求并发进行,使用Promise.all方法

1
2
3
4
5
6
async function dbFun() {
let docs = [{}, {}, {}]
let promises = docs.map(doc => dbFun.post)
let res = await Promise.all(promises)
console.log(res)
}

或者使用这个写法:

1
2
3
4
5
6
7
8
9
10
async function dbFun() {
let docs = [{}, {}, {}]
let promises = docs.map((doc) => db.post(doc))
let res = []
for (const promise of promises) {
res.push(await promise)
}
console.log(res)
}
// 这个方法太妙了

async函数的实现原理

async函数的实现原理就是将Generator函数和自动执行器包在一个函数里

1
2
3
4
5
6
7
8
9
10
11
async function fn(args) {
// ...
}

// 等同于

function fn(args) {
return spawn(function* () {
// ...
})
}

spawn函数就是自动执行器

按顺序完成异步操作

依次远程读取一组url,然后按照读取的顺序输出结果:

Promise写法:

1
2
3
4
5
6
7
8
9
function logInOrder() {
const textPromises = urls.map(url => {
return fetch(url).then(res=>res.text())
})
textPromises.reduce((chain, textPromise) => {
return chain.then(() => textPromise).then(text => console.log(text))

},Promise.resolve())
}

这种写法不美观,可读性差,用async函数实现:

1
2
3
4
5
6
async function logInOrder() {
for (const url of urls) {
const res = await fetch(url)
console.log(await res.text())
}
}

虽然代码被简化,但是这些操作都是继发的,非常浪费时间,可以将它改成并发的

1
2
3
4
5
6
7
8
9
async function logInOrder() {
const textPromises = urls.map(async url => {
const res = await fetch(url)
return res.text()
})
for (const textPromise of textPromises) {
console.log(await textPromise)
}
}