async函数

本系列属于阮一峰老师所著的ECMAScript 6 入门学习笔记javascript


概念

ES2017中引入了async函数,使异步操做变得更加方便。async是Generator函数的语法糖。java

const gen = function* (){
  const f1 = yield readFile('/etc/fstab')
  const f2 = yield readFile('/etc/shells')
}

// 写成async函数
const gen = async function (){
  const f1 = await readFile('/etc/fstab')
  const f2 = await readFile('/etc/shells')
}

比较可知,async函数将Generator函数的*替换成async,将yield替换成await ,仅此而已es6

async函数对Generator函数的改进有如下四点:shell

(1)内置执行器promise

Generator函数执行须要依靠执行器,而async函数自带执行器。也就是说async函数的执行与普通函数同样并发

gen()

(2)更好的语义化异步

asyncawait比起*yield,语义更加清楚。async表示函数有异步操做,await表示紧跟在后面的表达式须要等待结果async

(3)更广的适用性函数

co模块约定,yield命令后面只能是Thunk函数或者Promise对象,而async函数的await命令后面能够是Promise对象和原始类型的值(数值、字符串和布尔值,但这等同于同步操做)学习

(4)返回值是Promise

async函数的返回值是Promise对象,这比Generator函数的返回值是Iterator对象方便多了,咱们能够用then指定下一步的操做

基本用法

async函数会返回一个Promise对象,能够使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操做完成,再接着执行函数体后面的语句。

function timeout(ms){
  return new Promise(resolve =>{
    setTimeout(resolve,ms)
  })
}
// 因为async函数返回的是Promise函数,所以可改写为
async function timeout(ms){
  await new Promise(resolve =>{
    setTimeout(resolve,ms)
  })
}

async function asyncPrint(value,ms){
  await timeout(ms)
  console.log(value)
}

asyncPrint('hello world',50) // 在50毫秒以后输出hello world

// async函数的多种使用形式

// 函数声明
async function foo(){}

// 函数表达式
const foo = async function(){}

// 对象的方法
let obj = {async foo(){}};
obj.foo().then()

// class的方法
class Storage{
  constructor(){
    this.cachePromise = caches.open('avatars')
  }
  
  async getAvatar(name){
    const cache = await this.cachePromise
    return cache.match(`/avatars/${name}.jpg`)
  }
}

const storage = new Storage()
storage.getAvatar('jake').then()

// 箭头函数
const foo = async () => {}
返回Promise对象
// async函数内部return语句返回的值,会成为then方法回调函数的参数
async function f(){
  return 'hello world'
}

f().then(v => console.log(v)) // 'hello world'

// async函数内部抛出错误,会致使返回的Promise对象变成reject状态,抛出的错误对象被catch回调函数接收
async function f(){
  throw new Error('出错了')
}

f().then(v => console.log(v),e => console.log(e)) // Error:出错了

// async函数返回的Promise对象,必须等内部全部的await命令后面的Promise对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误
await命令
// 正常状况下,await命令后面是一个Promise对象。若是不是,会被转成一个当即resolve的Promise对象
async function f(){
  return await 123
}

f().then(v => console.log(v))

// await后面的Promise对象变为reject状态,则reject的参数会被catch方法的回调函数接收到
async function f(){
  await Promise.reject('出错了')
}

f().then(v => console.log(v)).catch(e => console.log(e)) // 出错了

// 只要有一个await语句后面的Promise变为reject,那么整个async函数都会中断执行
async function f(){
  await Promise.reject('出错了')
  await Promise.resolve('Hello world') // 不会执行
}

// 若是但愿前一个异步操做失败,也不中断后面的异步操做,这时能够把第一个await放在try...catch结构里面,这样不管这个异步操做是否成功,第二个await都会执行
async function f(){
  try{
    await Promise.reject('出错了')
  }catch(e){
  }
  return await Promise.resolve('hello world')
}

f().then(v => console.log(v)) // 'hello world'
  
// 另一种方法是await后面的Promise对象再跟一个catch方法,处理前面可能出现的错误
async function f(){
  await Promise.reject('出错了').catch(e => console.log(e))
  return await Promise.resolve('hello world')
}

f().then(v => console.log(v)) // 出错了 hello world

// 若是有多个await命令,能够统一放在try...catch结构中
使用注意点

(1)作好错误处理,最好把await命令放在try...catch代码块中

(2)多个await命令后面的异步操做,若是不存在继发关系,最好让它们同时触发

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

// 这两个独立的异步操做互不影响,被写成继发关系,这样比较耗时,可让他们同时触发,缩短程序的执行时间
// 写法一
let [foo,bar] = await Promise.all([getFoo(),getBar()])
// 写法二
let fooPromise = getFoo()
let barPromise = getBar()
let foo = await fooPromise
let bar = await barPromise

(3)await只能用在async函数中,若是用在普通函数,就会报错

async函数的实现原理

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

async function fn(args){
  // ...
}

// 等同于

function fn(args){
  return spawn(function* (){
    // ...
  })
}
// 其中spawn函数就是自动执行器
与其余异步方法的比较

如下例子比较async函数、Promise、Generator函数

// 某个DOM元素,部署了一系列的动画,前一个动画结束,才开始后一个。若是其中有一个动画出错,就再也不继续执行了,返回上一个成功执行的动画的返回值

// Promise的写法
function chainAnimationsPromise(elem,animations){
  // 变量ret用来保存上一个动画的返回值
  let ret = null
  // 新建一个空的Promise
  let p = Promise.resolve()
  // 使用then方法,添加全部动画
  for(let anim of animations){
    p = p.then(function(val){
      ret = val
      return anim(elem)
    })
  }
  // 返回一个部署了错误机制的Promise
  return p.catch(function(e){
    // 忽略错误,继续执行
  }).then(function(){
    return ret
  })
}
// Promise写法已经比回调函数的写法大大改进,但操做自己的语义不太容易看出来

// Generator函数的写法
function chainAnimationsGenerator(elem,animations){
  return spawn(function* (){
    let ret = null
    try{
      for(let anim of animations){
        ret = yield anim(elem)
      }
    }catch(e){
      // 忽略错误,继续执行
    }
    return ret
  })
}
// Generator函数须要一个spawn自动执行器,并且必须保证yield语句后面的表达式必须返回一个Promise

// async函数的写法
async function chainAnimationsAsync(elem,animations){
  let ret = null
  try{
    for(let anim of animations){
      ret = await anim(elem)
    }
  }catch(e){
    //忽略错误,继续执行
  }
  return ret
}
// async函数的实现最简洁,最符合语义,代码量少
实例:按顺序完成异步操做
// 一次远程读取一组URL,按照读取的顺序输出结果

// Promise的写法
function logInOrder(urls){
  // 远程读取全部URL
  const textPromises = urls.map(url =>{
    return fetch(url.then(response => response.text()))
  })
  // 按次序输出
  textPromises.reduce((chain,textPromise) => {
    return chain.then(() => textPromise).then(text => console.log(text))
  },Promise.resolve())
}

// async函数写法
async function logInOrder(urls){
  for(const url of urls){
    const response = await fetch(url)
    console.log(await response.text())
  }
}

// 这样简化了写法,可是全部操做都是继发,效率差,咱们须要并发请求
async function logInOrder(urls){
  // 并发读取远程URL
  const textPromises = urls.map(async url =>{
    const response = await fetch(url)
    return reponse.text()
  })
  // 按次序输出
  for(const textPromise of textPromises){
    console.log(await textPromise)
  }
}
// map方法的参数是async函数,但他是并发执行的,由于只有async函数内部是继发执行,外部不受影响。后面的for...of循环内部使用await,所以实现了按顺序输出
相关文章
相关标签/搜索