如何在 JavaScript 中以 Promise 形式和 async/await 形式重写回调函数

2025-06-04

如何在 JavaScript 中以 Promise 形式和 async/await 形式重写回调函数

最初发表于coreycleary.me。这篇文章来自我的内容博客。我每周或每两周发布一次新内容,如果您想直接在邮箱中收到我的文章,可以订阅我的新闻通讯!我还会定期发送一些小抄和其他免费赠品!

“你真的应该在这里使用 Promises 或 async/await 来让它更具可读性”

有多少次,为了得到问题的答案,你上传了一些代码片段,结果却有人纠缠你?现在,除了代码中已有的问题之外,你还需要学习和“修复”另一件事……

或者,在工作中重构现有的基于回调的代码库又如何呢?如何将它们转换为原生的 JavaScript Promises?能够使用现代 JavaScript 进行开发并开始使用这些async/await功能真是太棒了……

如果您知道如何避免回调,您可以在寻求帮助时在线发布您的代码,而无需人们要求您重写它并且实际上不回答您的问题。

如果你重构现有的代码库,那么代码的可读性会更高,你可以避免人们似乎仍在谈论的“回调地狱”,即使在 2019 年,Promises 已经在许多浏览器和 Node 中得到支持多年,并且async/await也得到许多版本的支持……

修复

让我们来看看如何将这些老式的回调转换为 Promises 和async/await版本。

这是演示回调 -> Promise 和回调 ->版本的代码链接async/await

回调版本

const callbackFn = (firstName, callback) => {
  setTimeout(() => {
    if (!firstName) return callback(new Error('no first name passed in!'))

    const fullName = `${firstName} Doe`

    return callback(fullName)
  }, 2000)
}

callbackFn('John', console.log)
callbackFn(null, console.log)
Enter fullscreen mode Exit fullscreen mode

你会注意到,我们在这里使用了setTimeout()函数,以便使函数异步。除了 之外setTimeout(),你在现实世界中可能看到的其他异步操作包括:AJAX 和 HTTP 调用、数据库调用、文件系统调用(在 Node 中,如果没有同步版本)等等。

在这个函数中,如果 first name 参数为空,我们就“拒绝”它。当我们传入firstName参数时,回调函数(几乎总是基于回调的函数参数列表中的最后一个参数)会被调用,并在 设置的 2 秒后返回我们的值setTimeout()

如果我们不传递回调,就会出现TypeError: callback is not a function错误。

承诺版本

这是该函数的基于 Promise 的版本:

const promiseFn = firstName => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (!firstName) reject(new Error('no first name passed in!'))

      const fullName = `${firstName} Doe`  

      resolve(fullName)
    }, 2000)
  })
}

promiseFn('Jane').then(console.log)
promiseFn().catch(console.log)
Enter fullscreen mode Exit fullscreen mode

转换为基于 Promise 的函数实际上非常简单。下图直观地解释了这一点: 首先,我们删除了回调参数。然后,我们添加代码,以便从基于 Promise 的函数返回一个 。错误回调变成了,而“快乐路径”回调变成了

new Promiserejectresolve

当我们调用时promiseFn,顺利路径的结果将显示在中.then(),而错误场景将显示在中.catch()

将函数以 Promise 形式呈现的好处在于,如果我们不想的话,实际上并不需要“将其变成 async/await 版本”。调用/执行函数时,我们只需使用async/await关键字即可,如下所示:

const result = (async () => {
  try {
    console.log(await promiseFn('Jim')) 
  } catch (e) {
    console.log(e)
  }

  try {
    console.log(await promiseFn()) 
  } catch (e) {
    console.log(e)
  }
})()
Enter fullscreen mode Exit fullscreen mode

附注:这里我将函数调用包装在了 IIFE 中——(async () => {....})()如果你没见过的话,它就是 IIFE。这仅仅是因为我们需要将调用包装await在一个使用了关键字的函数中async,并且我们还希望“立即调用”该函数(IIFE = “立即调用函数执行”)以便调用它。

这里没有回调,没有.then().catch(),我们只是使用一个try/catch块并调用promiseFn()。Promise 的拒绝将被该块捕获catch

注意:除 Internet Explorer 外,大多数主流浏览器的半最新版本async/await均支持此功能。Node 自 7.6.0 版本起就已支持此功能。

async/await 版本

但是如果我们想直接将回调函数转换为该async/await函数的某个版本呢?而不是直接使用 Promises?

async/await是围绕 Promises 的语法糖,因此它在底层使用了它们。转换方法如下:

const timeout = ms => {
  return new Promise(resolve => setTimeout(resolve, ms))
}

const asyncAwaitFn = async firstName => {
  await timeout(2000) // using timeout like this makes it easier to demonstrate callback -> async/await conversion

  if (!firstName) throw new Error('no first name passed in!')

  const fullName = `${firstName} Doe`

  return fullName
}

const res = (async () => {
  try {
    console.log(await asyncAwaitFn('Jack')) 
  } catch (e) {
    console.log(e)
  }

  try {
    console.log(await asyncAwaitFn()) 
  } catch (e) {
    console.log(e)
  }
})()
Enter fullscreen mode Exit fullscreen mode

使用下图来理解如何从回调到async


与转换为基于 Promise 的版本类似,我们删除了传递给原始函数的回调,以及函数主体中的参数调用。接下来,我们将async关键字添加到函数声明的开头。最后,当遇到错误场景时,我们抛出一个Error,这会导致 Promise 被拒绝(在调用函数时被捕获到代码块中),而在快乐路径场景中,catch我们只需返回。fullName

请注意,async所有函数都会返回 Promise,因此当您使用时,return您只是在解决 Promise。

总结

下次你需要将基于回调的函数转换为基于 Promise 或基于async/await的版本时,可以使用本文中的可视化图表快速轻松地完成。如果你需要一些代码来进一步理解这些概念,这里再次提供了代码演示链接,演示了回调 -> Promise 以及回调 ->async/await的版本。

回调地狱现在已经消失!

我计划在未来发布更多内容,因此如果您发现这有帮助并且希望将其直接发送到您的收件箱而不必记得再次回到这里查看,这里是时事通讯的链接!

文章来源:https://dev.to/ccleary00/how-to-rewrite-a-callback-function-in-promise-form-and-asyncawait-form-in-javascript-410e
PREV
当没有“标准方式”时 Express REST API 的项目结构
NEXT
构建 RESTful API 的最佳实践