ES6 新特性之 async 函数

基本用法

ES2017 标准引入了 async 函数,使得异步操作变得更加方便

async function f() {
  return await 'hello world'
}

正常情况下,await 命令后面是一个 Promise 对象,返回该对象的结果

async function f() {
  return await Promise.resolve('hello world')
}

如果 await 命令后面不是 Promise 对象,就直接返回对应的值

async function f() {
  return await 'hello world'
  // 等同于
  return 'hello world'
}

async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数
async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数

async function f() {
  return await Promise.resolve('hello world')
}

f().then(res => { console.log(res) })
// hello world

函数执行的时候,一旦遇到 await 就会先返回,等到异步操作完成,再接着执行函数体内后面的语句

async function dataApi (params) {
  return await new Promise(res => {
    setTimeout(res, 1000, params)
  })
}
async function asyncFun () {
  console.log(0)
  await dataApi('hello')
  console.log(1)
  await dataApi('world')
  console.log(2)
}

asyncFun()
// 0
// Promise·{<pending>}
// 1
// 2

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 () => {}

async 函数内部抛出错误,会导致返回的 Promise 对象变为 reject 状态,抛出的错误对象会被 catch 方法回调函数接收到

async function f() {
  throw new Error('出错了')
}

f().catch(e => console.log(e))
// Error: 出错了

async 函数返回的 Promise 对象,只有 async 当函数内部的异步操作执行完,才会执行 then 方法指定的回调函数,除非遇到 return 语句或者抛出错误

async function getTitle(url) {
  let response = await fetch(url)
  let html = await response.text()
  return html.match(/<title>([\s\S]+)<\/title>/i)[1]
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"

上面代码中,函数 getTitle 内部有三个操作:抓取网页、取出文本、匹配页面标题。只有这三个操作全部完成,才会执行 then 方法里面的 console.log

注意事项

多个 await 命令后面的异步操作,如果不存在继发关系,最好让它们同时触发

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

上面代码中,getFoo 和 getBar 是两个独立的异步操作(即互不依赖),被写成继发关系,这样比较耗时,因为只有 getFoo 完成以后,才会执行 getBar,完全可以让它们同时触发

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

上面两种写法,getFoo 和 getBar 都是同时触发,这样就会缩短程序的执行时间

async 函数的实现原理

async 函数其实就是 Generator 函数的语法糖

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

async function fn(args) {
  // ...
}
// 等同于
function fn(args) {
  return spawn(function* () {
    // ...
  })
}

上面的 spawn 就是一个自动执行器,下面给出 spawn 函数的实现(简化版,不考虑异常)

function spawn(genF) {
  return new Promise(resolve => {
    let gen = genF()
    let step = function (v) {
      let next = gen.next(v)
      if(next.done)  return resolve(next.value)
      else step(next.value)
    }
    step()
  })
}

下面是一个测试示例

function fn() {
  return spawn(function* () {
    let h = yield 'hello'
    let w = yield 'world'
    console.log(h, w)
    return '!'
  })
}
// 等同于
async function fn(args) {
  let h = await 'hello'
  let w = await 'world'
  console.log(h, w)
  return '!'
}

fn()
// hello world

本文转载自 http://es6.ruanyifeng.com/#docs/async 并进行整理

除特殊说明外本人博客均属原创,转载请注明出处:http://blog.johnhan.cn/blog_1048.html
鄂ICP备17018604号-1  鄂公网安备42060702000030号