ES7异步操作-async函数
概述
async函数是Generator函数的语法糖,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await
使用Generator 函数,依次读取两个文件
let fs = require('fs')
let readFile = function(filename){
return new Promise(function(resolve, reject){
fs.readFile(filename, function(error, contents){
if(error) return reject(error)
resolve(contents)
})
})
}
// 自定义一个执行器
function run(task) {
let iterator = task(); // 生成器函数task的迭代器
let result = iterator.next(); // 启动任务
function step() { // 递归调用step函数,保持对next()的调用
if(!result.done) {
let promise = Promise.resolve(result.value);
promise.then(function(value){
result = iterator.next(value);
step();
}).catch(function(error){
result = iterator.throw(error);
step();
})
}
}
step(); // 启动处理过程
}
let g = function *() {
let ret1 = yield readFile('./a.json')
let ret2 = yield readFile('./b.json')
console.log(ret1.toString(), ret2.toString())
}
run(g)
使用Async 函数,依次读取两个文件
let fs = require('fs')
let readFile = function(filename){
return new Promise(function(resolve, reject){
fs.readFile(filename, function(error, contents){
if(error) return reject(error)
resolve(contents)
})
})
}
let asyncReadFile = async function() {
let ret1 = await readFile('./a.json')
let ret2 = await readFile('./b.json')
console.log(ret1.toString(), ret2.toString())
}
asyncReadFile()
run()执行器在上一篇文章Promise和异步编程中已经介绍过了,从调用方式上看,两者确实很相似。Async函数对 Generator 函数的改进,主要体现在以下四点:
1. 内置执行器
Generator 函数的执行必须靠执行器,就是示例中的run()函数。而Async函数自带执行器,所以Async函数的执行开起来和普通函数一模一样
语义化
async-await相比星号和yield的语义更加清晰,async表示函数里有异步操作,await表示等待表达式结果
适用性广
async函数的await命令后面,可以是Promise 对象,也可以是原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)
返回值是Promise
async函数的返回值是一个Promise,所以可以用then()方法指定下一步操作,而Generator 函数的返回值是 Iterator 对象。
多种使用形式
async 函数有多种使用形式
// 函数声明
async function test(){}
// 函数表达式
const test = async function(){}
// 箭头函数
const test = async () => {}
// 对象的方法
let obj = {
async getName(){}
}
// Class方法
class Person() {
async getInfo(url) {
let response = await fetch(url)
return await response.text()
}
}
语法
async函数返回一个Promise对象,所以可以使用then()方法添加回调函数,async函数内部return语句返回的值,会成为then方法回调函数的参数
async function getData(url) {
let response = await fetch(url)
let result = await response.text()
return result
}
getData('https://86886.wang/api/articles').then(function(result){
console.log(result)
})
示例向一个地址请求数据,请求结束后打印返回的数据
Promise 对象的状态变化
async函数返回的Promise对象,必须要等到await命令后面的Promise对象执行完成,才会发生发生状态改变,除非遇到return语句或者抛出错误。
上面的示例中,必须要等待getData内部的await都执行完,才会执行getData的then()方法
awiat命令
通常await命令后面是一个Promsie对象,如果不是会被转换成一个立即resolve的Promsie对象
async function test() {
return await 'hello'
}
test().then((value) => {
console.log(value) // hello
})
await后面的'hello'会被转换成Promise对象,并立即resolve
await命令后面的Promise对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到
async function test() {
return await Promise.reject('bad request')
}
test().catch((value) => {
console.log(value) // bad request
})
Promise.reject()前面的return可以省略,错误仍然可以被catch接收到。但是如果Promise.resolve()前面的return省略了,then方法是接收不到返回值的
async函数里,只要有一个awiat语句后的Promsie变成reject,那么整个async函数都会中断执行
async function test() {
let r1 = await Promise.reject('bad request')
let r2 = await Promise.resolve('hello') // 不会执行
}
如果需要即使异步操作失败也不要中断后面的异步操作,可把await放到try-catch块里
async function test() {
try{
await Promise.reject('bad request')
}catch(e){}
return await Promise.resolve('hello') // 正常执行
}
test().then(v => console.log(v)) // hello
也可以在await后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误
async function test() {
await Promise.reject('bad request').catch(e => console.log(e))
return await Promise.resolve('hello') // 正常执行
}
test().then(v => console.log(v))
// bad request
// hello
错误处理
如果async函数内部抛出错误,会导致返回的Promise对象变为reject状态,错误对象会被catch()方法接收到
async function test() {
throw new Error('bad request')
}
test().then((value) => {
console.log(value)
},(error) => {
console.log(error.message) // bad request
})
async function test() {
await new Promise(function(resolve, reject){
throw new Error('bad request')
})
}
test().then((value) => {
console.log(value)
},(error) => {
console.log(error.message) // bad request
})
防止出错的方法前面说过了,将其放在try...catch代码块之中
注意事项
- 由于await命令后面的Promsie对象,运行结果可能是rejected,所以最好把await命令放到try-catch块中
async function test(url) {
try{
await fetch(url)
}catch(err){
console.log(err)
}
}
// 或者这样
async function test(url) {
await fetch(url).catch(err => console.log(err))
}
- 对于多个await后面的异步操作,如果不存在继发关系,最好让它们并发执行,这样可以减少耗时
// 继发
let ret1 = await getArticles()
let ret2 = await getTags()
// 并发
let [ret1, ret2] = await Promise.all([getArticles(), getTags()])
- async函数在forEach循环中可能会出错,可以使用for-of循环
// 可能出错,无法知道异步函数是否执行成功
function test(db) {
let arr = [{}, {}, {}]
arr.forEach(async function(item) {
await db.post(item)
})
}
// 不会出错,可以知道异步执行结果
async function test(db) {
let arr = [{},{},{}]
for(let item of arr) {
await db.post(item)
}
}
在forEach中可能出错的原因是,所有异步操作都是并发的,而在for-of中所有异步操作是继发的
如果确实需要在循环中,并发执行多个异步操作,可以使用Promise.all()方法解决
async function test(db) {
let arr = [{}, {}, {}]
let promises = arr.map(item => db.post(item))
let results = await Promise.all(promises)
console.log(results)
}