async函数解析

转载请注明出处:async函数解析html

async函数是基于Generator函数实现的,也就是说是Generator函数的语法糖。在以前的文章有介绍过Generator函数语法和异步应用,若是对其不了解的,能够先看看关于Generator函数的文章,这样学习async函数的难度就不会太大。es6

传送门: Generator语法解析 Generator函数异步应用并发

接下来会用一些篇幅说明一下async函数,文末会给出async函数的参考学习文章。异步


文章目录

  1. 含义
  2. 基本语法
  3. 错误处理
  4. 异步应用

含义

咱们知道,调用Generator函数不会当即执行,而是返回遍历器对象。疲于手动执行遍历器对象,所以就有了thunk(thunkify)函数结合run函数来实现自动流程管理。或者,使用co模块来实现自动流程管理,使Generator函数的使用更加方便。async

而async函数ES2017标准引入的语法,是Generator函数的语法糖,所以其相对于Generator函数,具备如下基本特色。函数

内置执行器:使用async函数能够像使用普通函数同样,直接调用便可执行。不用像Generator函数同样使用co模块来实现流程控制。学习

语义化更强:async关键字表示是一个异步的函数,await表示须要等待执行。相对于yield表达式,语义化更强。this

返回值是Promise:async函数返回值是Promise对象,这比Generator函数的返回值是Iterator对象方便多了,可使用then方法来指定下一步的操做。code

基本语法

使用async关键字代表函数是一个async函数,内部使用await关键字代表须要等待异步任务结束后才继续往下执行。orm

async function as () {
  return 123
}
as().then(data => {
  console.log(data)
})

从上面代码能够看出,调用async函数会返回Promise对象,返回值能够做为then方法成功处理函数的参数值。

若是在async内部若是抛出错误或者出现异常,会被then方法的错误处理函数捕获或者catch方法捕获。

async function as () {
  throw new Error('出错拉!')
}
as().then(data => {
  console.log(data)
}).catch(err => {
  console.log(err)
})  // Error: xixi, catch方法捕获到错误

另外,async函数内部可使用await关键字,表示后面的表达式是异步任务。await关键字后边的表达式能够是一个Promise对象,或者简单(复杂)数据类型(Number, String, RegExp, Boolean, Array, Objext)。若是是简单(复杂)数据类型,async函数会隐式调用Promise.resolve方法将其转换为Pormise对象。

function foo () {
  return new Promise((resolve, reject) => {
    window.setTimeout(() => {
      resolve('async')
    }, 1000)
  })
}
async function as () {
  const data = await foo()  //foo函数使用setTimeout来模拟异步。
  console.log(data)
}
as()  // async

async function as () {
  return await 123   //若是是其余数据类型,也是如此。
}
as().then(data => {
  console.log(data)
})  // 123

若是await关键字后面的表达式是非Promise、非thenable的普通的值,则会隐式调用Promise.resolve方法将其转换为Promise对象,await关键字会在内部调用then方法将resolve的值返回。

await内部实现大体以下
function await (data) {
  return new Promise((resolve, reject) => {
    resolve(data)
  }).then(data => {
    return data
  })
}

总之,await关键字是then方法的语法糖,会将resolve的值传递出来。

另外,若是在await关键字后的表达式抛出了错误,会使async函数返回的Promise对象从pending状态转变为reject状态,进而被catch方法捕获到错误。

function foo () {
  throw new Error('err')
}
async function as () {
  await foo()
}
as().then(data => {})
    .catch(err => {
      console.log(err);
    })  // as函数返回的Promise对象从pending状态变为reject状态。

若是某个await关键字后面的表达式抛出错误,async函数的状态就会变为reject,那么函数就会暂停执行,后面的表达式就不会在继续执行。由于Promise函数有一个特色是,一旦状态改变,就不会再变,以后在调用也是保持同一个状态。

function foo () {
  throw new Error('err')
}
async function as () {
  await foo()
  return Promise.resolve('succ') // 不会执行到这里,由于Promise对象的状态一旦改变就不会在变了,所以不执行。
}
as().then(data => {})
    .catch(err => {
      console.log(err);
    })

由于async函数默认状况下返回的是Promise对象,所以能够将async函数做为await关键字后面的表达式。async函数调用另外一个async函数会更加方便,不会像Generator函数须要使用yield*表达式来调用。

async function foo () {
  return Promise.resolve('async')
}
async function as () {
  return await foo()   // 调用foo函数会返回Promise对象
}
as().then(data => {
  console.log(data)
})

另外,若是async函数内部没有抛出错误,函数正常执行。那么每个await关键字后面的异步任务会继发执行。也就是说,一个异步任务结束以后才会执行另一个异步任务,而不是并发执行。

async function foo () {
  return new Promise((resolve, reject) => {
    window.setTimeout(() => {
      resolve(10)
    }, 1000)
  })
}
async function bar () {
  return new Promise((resolve, reject) => {
    window.setTimeout(() => {
      resolve(20)
    }, 2000)
  })
}
async function as () {
  let t1 = Date.now()
  const a = await foo()
  const b = await bar()
  let t2 = Date.now()
  console.log(t2 - t1)  // 有偏差,大概3004ms
  return a + b
}
as().then(data => {
  console.log(data)   // 大概3s后输入30
})

若是两个异步任务互不依赖,若是按照上面的代码,两个异步任务继发执行,这样作的缺点是时间浪费了。原本200ms能够完成的两个异步任务,却用了400ms。所以可让两个互不依赖的异步任务同时触发。有两种方法:

// 方法一:
async function as () {
  const t1 = Date.now()
  const [fo, ba] = [foo(), bar()]
  // 以上两个函数同时执行,并将结果做为await关键字的表达式
  const a = await fo
  const b = await ba
  const t2 = Date.now()
  console.log(t2 - t1)
  return a + b
}

// 写法二:结合使用Promise.all等待全部异步任务完成后才会返回
async function as () {
  const t1 = Date.now()
  const arr = await Promise.all([foo(), bar()])
  const t2 = Date.now()
  console.log(t2 - t1)
  return arr[0] + arr[1]
}
as().then(data => {
  console.log(data)  // 30
})

错误处理

因为async函数内部的异步任务一旦出现错误,那么就等同于async函数返回的Promise对象被reject。所以,为了防止异步任务出现错误,可使用try...catch来捕获错误,使async函数内部能够正常执行。

async function as () {
  let a = 0
  let b = 0
  try {
    a = await foo()
    b = await bar()
  } catch (e) {}
  return a + b
}
as().then(data => {
  console.log(data) // 30
})

咱们知道,try...catch只能用于处理同步的操做,对于异步任务没法捕获到错误。而await关键字可以暂停函数处理,等待异步任务结束以后返回。所以在async函数中使用try...catch结合await关键字捕获异步错误是一个不错的方法。

异步应用

咱们来看看使用Promise、Generator、async来实现异步应用的差异。接下来会使用setTimeout来模拟异步。

先来看两个基础函数

function foo (obj) {
  return new Promise((resolve, reject) => {
    window.setTimeout(() => {
      let data = {
        height: 180
      }
      data = Object.assign({}, obj, data)
      resolve(data)
    }, 1000)
  })
}
function bar (obj) {
  return new Promise((resolve, reject) => {
    window.setTimeout(() => {
      let data = {
        talk () {
          console.log(this.name, this.height);
        }
      }
      data = Object.assign({}, obj, data)
      resolve(data)
    }, 1500)
  })
}

两个函数内部都返回了Promise实例对象,经过Object.assign来合并传递过来的参数。

首先看看纯Promise对象的实现。

function main () {
  return new Promise((resolve, reject) => {
    const data = {
      name: 'keith'
    }
    resolve(data)
  })
}
main().then(data => {
  foo(data).then(res => {
    bar(res).then(data => {
      return data.talk()   // keith 180
    })
  })
})

调用过程当中就是在不断使用then方法,不够直观,操做自己的语义不太容易看出来。并且有可能出现回调地狱的风险。

接下来看看Generator函数的实现。因为Generator函数的调用须要手动执行,所以写了run函数来实现流程自动控制。

function *gen () {
  const data = {
    name: 'keith'
  }
  const fooData = yield foo(data)
  const barData = yield bar(fooData)
  return barData.talk()
}
function run (gen) {
  const g = gen()
  const next = data => {
    let result = g.next(data)
    if (result.done) return result.value
    result.value.then(data => {
      next(data)
    })
  }
  next()
}
run(gen)

使用run函数来实现自动流程控制,Generator函数的好处相对于Promise对象来讲,使得异步的过程同步化,同时少了回调地狱的风险。可是缺点是须要使用像run函数或者co模块来实现流程控制。

接下来使用async函数来实现看看。

async function main () {
  const data = {
    name: 'keith'
  }
  const fooData = await foo(data)
  const barData = await bar(fooData)
  return barData
}
main().then(data => {
  data.talk()
})

从上面代码中,能够看出,使用async函数的代码量最少,并且使得异步过程同步化,更进一步,async函数内置执行器。调用的方法更加简洁。


ok,差很少就这样了,稍微总结一下。

  1. async函数是基于Generator函数实现的,是Generator函数的语法糖。其内置执行器,调用后返回Promise对象,所以能够像普通韩式同样使用。
  2. async函数内部抛出错误或者await关键字后面的表达式抛出错误,会使async函数返回的Promise对象从pending状态变为reject状态,从而能够被catch方法捕获错误。并且,Promise对象的状态一旦改变就不会再变,以后的异步任务就不会执行了。
  3. await关键字后面的表达式能够是Promise对象,也能够是其余数据类型。若是是其余数据类型,则会经过Promise.resolve将其转换为Promise对象
  4. async函数内部若是有多个await关键字,其后的异步任务会继发执行。若是每个异步任务不相互依赖,则可使用Promise.all让其并发执行,这样能够在一样的时间里完成多个异步任务,提升函数执行效率。
  5. 对于async内部抛出的错误,可使用try...catch来捕获异常。虽然try...catch只能用于捕获同步任务,可是await关键字可使得异步任务同步化,所以能够结合try...catch和await关键字捕获异步任务。

参考资料:

  1. async 函数
  2. async 函数的含义和用法
相关文章
相关标签/搜索