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代码块之中

注意事项

  1. 由于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))
}
  1. 对于多个await后面的异步操作,如果不存在继发关系,最好让它们并发执行,这样可以减少耗时
// 继发
let ret1 = await getArticles()
let ret2 = await getTags()

// 并发
let [ret1, ret2] = await Promise.all([getArticles(), getTags()])
  1. 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)
}
posted @ 2021-09-29 13:02  wmui  阅读(155)  评论(0编辑  收藏  举报