5 年前 Web

利用 ES7 的 async 和 await 解决回调函数嵌套问题

由于 Javascript 执行环境是“单线程”的,想要实现异步编程,就会用到回调函数。如果一些异步请求之间存在依赖,或者服务端使用 node 进行大量的 io 操作时就会出现回调函数嵌套的情况,代码就变成了横向发展,不利于阅读,维护起来麻烦。

//回调函数嵌套的情况
fs.readFile(fileA, function (err, data) {
  fs.readFile(fileB, function (err, data) {
    // ...
  })
})

解决方案

解决回调函数嵌套问题可以使用:

  1. Generator

  2. co

  3. Promise

  4. async / await

ES7 中有了更加标准的解决方案,新增了 async/await 两个关键词。async 可以声明一个异步函数,此函数需要返回一个 Promise 对象。await 可以等待一个 promise 对象 resolve,并拿到结果。 相对于前面的3种方法,async 实现最简洁,符合语义,几乎没有语义不相关的代码。

async

async 是一个关键字,通常是放在函数声明之前,目的是将函数转换成异步函数,并返回 promise 对象。

// 将 a 函数转成异步函数,返回一个 promise 对象
async function a () { return 'a' }
// 上面的代码等同于
function a () { return Promise.resolve('a') }
// 调用
a().then(console.log) // 'a'

await

await 也是一个关键字,一般在使用了 async 的函数里定义,放在任何基于异步声明的函数之前,作用是暂停代码在该行上,直到 promise 完成,然后返回结果值。

实践

数据库的增删改查

async deleteTag ({ id }) {
  const result = await Tag.remove({ _id: id })  
  if (!result) {
    throw new Error('error')
  }
  return result
}

async createPost (post) {
 const doc = new Post(post)
 const result = await doc.save()
 await Archive.updatePostId(result.id)
 return result
}

deleteTag().then(result => { //... })
createPost().then(result => { //... })

deleteTag 函数通过 id 删除对应的标签,createPost 函数创建文章,成功创建后再更新存档数据。前面加上一个 async(表示一个异步函数),里面使用 await 语句(相当于暂停标志),就可以像写同步的代码一样解决回调函数嵌套的问题。

实现 sleep 函数

function sleep (timeout){
  return new Promise(function(resolve){
    setTimeout(function(){
      resolve()
    }, timeout)
  })
}

;(async function(){
  console.log(new Date())
  await sleep(3000)
  console.log(new Date())
})()

输出第一个 log 信息后,遇到 await 语句,执行 sleep 方法,然后会等待3秒才继续执行下面的代码。

注意事项

由于它们属于 ES7 ,仅仅只是语法糖,想要在前端或者 node 服务端使用需要 babel 进行转译,或者使用 polyfill