JS异步编程之generator与async/await语法糖详解

作者:小括号时间:2026-10-25 22:36:33浏览:341

本文深入解析JavaScript异步编程的演进,从Generator函数的执行机制、yield暂停与传参特性,到基于Promise的自动执行器实现,最终过渡到ES2017标准的async/await语法。通过对比传统回调、Promise链式调用及Generator方案,阐述async/await如何实现类同步的扁平化异步代码编写体验。

Generator 异步方案

相较于传统的回调函数处理异步调用的方式,Promise 的主要优势在于支持链式调用,从而解决了回调地狱(Callback Hell)的问题。然而,即便消除了嵌套,链式调用中依然充斥着大量的回调函数,其可读性仍未能达到传统同步代码的标准。若采用如下所述的异步代码编写方式,代码结构将更加简洁且易于阅读。

// like sync mode

try{
  const value1 = ajax('/api/url1')
  console.log(value1)
  const value2 = ajax('/api/url1')
  console.log(value2)
  const value3 = ajax('/api/url1')
  console.log(value3)
  const value4 = ajax('/api/url1')
  console.log(value4)
  const value5 = ajax('/api/url1')
  console.log(value5)
}catch(err){
  console.log(err)
}

ES2015 标准引入了生成器函数(Generator Function)。其语法与普通函数的区别在于,在 function 关键字之后与函数名之前,需添加一个“*”符号作为生成器函数的标识。

调用生成器函数时,函数体并不会立即执行,而是返回一个生成器对象。只有当手动调用该对象的 next 方法时,函数体才会开始执行。在函数内部,可以使用 yield 关键字向外返回一个值,并通过 next 方法的返回值获取该值。此外,返回值对象中还包含一个 done 属性,用于指示生成器是否已执行完毕。

yield 关键字不会像 return 那样终止函数的执行,它仅暂停函数的执行。当外部再次调用 next 方法时,函数将从 yield 所在的位置继续向下执行。

function * foo () {
  console.log('start')
    yield 'foo'
}

​​​​​​​const generator = foo()

const result = generator.next()

若在调用 next 方法时传入参数,该参数将作为上一次 yield 表达式的返回值。

function * foo () {
  console.log('start')
    // 我可以在这里接收next传入的参数
    const res = yield 'foo'
  console.log(res) // 这是我传入的参数
}

const generator = foo()

const result = generator.next('这是我传入的参数')
console.log(result) // { value: 'foo', done: false }

如果调用生成器函数的 throw 方法,该方法会在生成器函数内部抛出一个异常。

function * foo () {
  console.log('start')
  // 我可以在这里接收next传入的参数
  try {
    const res = yield 'foo'
    console.log(res) // 这是我传入的参数
  } catch (err) {
    console.log(err.message) // 抛出错误
  }
}

const generator = foo()

const result = generator.next('这是我传入的参数')
console.log(result)

generator.throw(new Error('抛出错误'))

结合生成器函数与 Promise 来实现异步编程,可以获得更佳的开发体验。

function ajax(url) {
  return new Promise((resove, reject) => {
    var xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    // 新方法可以直接接受一个j对象
    xhr.responseType = 'json'
    xhr.onload = function () {
      if (this.status === 200) {
        resove(this.response)
      } else {
        reject(new Error(this.statusText))
      }
    }
    xhr.send()
  })
}

function* main() {
  const user1 = yield ajax('/json1.json')
  console.log(user1)

  const user2 = yield ajax('/json2.json')
  console.log(user2)

  const user3 = yield ajax('/json3.json')
  console.log(user3)
}

const g = main()
const result = g.next()

result.value.then(data => {
  const result2 = g.next(data)

  if (result2.done) return
  result2.value.then(data2 => {
    const result3 = g.next(data2)

    if (result3.done) return
    result3.value.then(data3 => {
      g.next(data3)
    })
  })
})

显然,生成器的执行器可以通过递归方式来实现调用逻辑。

const g = main()

function handleResult(result) {
  if (result.done) return
  result.value.then(data => {
    handleResult(g.next(data))
  }, err => {
    g.throw(err)
  })
}

handleResult(g.next())

由于生成器函数的调用模式具有相似性,我们可以封装一个通用的执行器函数。

function co(generator) {
  const g = generator()

  function handleResult(result) {
    if (result.done) return
    result.value.then(data => {
      handleResult(g.next(data))
    }, err => {
      g.throw(err)
    })
  }

  handleResult(g.next())
}


co(main)

社区中已经存在较为完善的此类执行器库,例如 co。在 2015 年之前,co 方案非常流行。随着 ES2017 中 async/await 语法糖的推出,Generator 方案的普及度有所下降。使用 Generator 方法最显著的变化是使异步调用回归到了扁平化的结构。

async/await

引入 Generator 后,JavaScript 异步编程的体验已接近同步代码,但手动编写执行器函数较为繁琐。ES2017 标准新增了 async 函数,它同样提供了扁平化的编程体验,并且是语言层面标准的异步编程语法。实际上,async 函数是 Generator 函数更便捷的语法糖,因此在语法上与 Generator 函数类似。

async function main() {
  try {
    const user1 = await ajax('/json1.json')
    console.log(user1)

    const user2 = await ajax('/json2.json')
    console.log(user2)

    const user3 = await ajax('/json3.json')
    console.log(user3)
  } catch (error) {
    console.log(error)
  }
}

main()

async 函数返回一个 Promise 对象,这有利于对整体代码流程进行更清晰的控制。

promise.then(() => {
  console.log('all completed')
}).catch(err => {
  console.log(err)
})

至此,关于 JS 异步编程中 Generator 与 async/await 语法糖的详解介绍完毕。如需了解更多相关内容,可搜索本站历史文章或继续浏览后续文章,感谢大家的支持!

作者最新文章