在以前的 JavaScript 版本中,开发者使用回调函数或者 Promise 来实现异步编程。但是这种编程方式有时会变得非常复杂和难以调试。随着 ECMAScript 9 的发布,JavaScript 开发人员可以使用 async/await 来简化异步编程,使代码更加易读易写。
本文将介绍 async/await 的使用方式,并讲述一些最佳实践。
什么是 async/await?
async/await 是 ECMAScript 7 中引入了异步编程的一种新方式,它将异步的流程变得像同步一样的写法。async/await 依靠 Promises 来工作,它使得异步操作在代码中以同步的风格展现,并且提供了更加简单、容易理解的编程模型。
async/await是两个关键字:
- async - 函数标记为异步函数
- await - 等待异步操作完成
当调用一个异步操作时,执行控制权交给了这个异步操作,并且执行异步操作的代码将继续执行。在这个过程中,被标记为 async 的函数不会阻塞,而是会立即返回一个 Promise 对象。异步操作完成后会继续执行这个 async 函数,并且将异步结果包含在 Promise 对象中返回。
让我们来看一个简单的例子:
async function getData() { const data = await fetch('/api/data'); return data.json(); } getData().then(data => console.log(data));
在这个例子中,我们定义了一个名为 getData
的函数,它标记为 async。这个函数使用 fetch API 发起了一个 HTTP 请求。await 关键字暂停了函数的执行,直到 fetch
请求完成并返回数据。在这之后,我们将数据解析为 JSON 格式并返回一个 Promise 对象。
在我们调用 getData()
的时候,这个函数立即返回一个 Promise 对象。在异步操作结束之后,Promise 对象将被解析为我们所需的数据,并被传递给 then
。
编写简单易懂的异步程序
async/await 简化了异步编程,让代码变得简单易懂。例如,我们可以将一个 Promise 链写成如下所示的代码:
fetch('/api/data') .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error(error));
使用 async/await 语法,我们可以将这个 Promise 链转换为一个结构更加清晰、易于阅读、并且更容易处理错误的代码:
-- -------------------- ---- ------- ----- -------- --------- - --- - ----- -------- - ----- ------------------- ----- ---- - ----- ---------------- ------------------ - ----- ------- - --------------------- - - ----------
with 异步函数(async function)的开发者可以使用 try-catch 块捕获异步操作的异常,并且是用 Promise 链实现的同时能够获得更好的可读性。
最佳实践
以下是一些在使用 async/await 时值得关注的最佳实践。
1. 让异步操作以串行方式执行
async/await 允许你将异步操作写成一个类似于同步代码的方式。但是在这个过程中,我们需要非常小心地处理异步操作的执行顺序,避免让多个异步操作同时进行。
以这个简单的例子为例,我们可以很容易地把两个异步任务定义在同一个异步函数中:
async function getUserData() { const user = await fetch('/api/user').then(response => response.json()); const data = await fetch(`/api/data/${user.id}`).then(response => response.json()); console.log(user, data); }
在这个函数中,我们依次执行了两条异步请求。需要注意的重点是,我们在获取用户信息之后才会请求相应的数据。这种顺序对于保持异步操作的同步执行非常重要。
如果我们使用传统的 Promise 风格来编写这个例子,代码将变得更加难懂:
-- -------------------- ---- ------- -------- ------------- - --- ----- ----- ------ ------------------ -------------- -- ---------------- ------------ -- - ---- - ------- ------ ------------------------------ -- -------------- -- ---------------- ------------ -- - ---- - ------- ----------------- ------ -- ------------ -- ---------------------- -
异步编程非常容易被写成回调风格或者 Promise 风格,但是使用 async/await 可以帮助我们减轻这种程度并且保持代码的易读性。
2. 使用 asyn/await 写代码的时候尽量精细化拆分
当我们在使用 async/await 时,为了保持一致性和可维护性,建议我们将异步操作的各个部分都划分得足够细致。一个方法(method)能够执行的最好限制是仅仅执行一个异步操作。如果不能够满足这种限制,可以将这个方法写成与上下文环境无关的函数。
例如,这个函数同时发送了多个 webhooks:
-- -------------------- ---- ------- ----- -------- ------------------- - ----- ----- - ----- -------------------- -------------- -- ----------------- ------------------- ---- -- - ----- ---------------- - ------- --------------- ----- -------------------------------------- - ------- ------- ----- ---------------- -------- ---------------- --- -------- - --------------- ------------------ - --- --- -
这个函数发送了多个异步请求,这些请求相互独立,可以异步进行。但是这里存在一个问题:因为 users.forEach
中的回调函数是异步的,每次执行都会产生一个新的 Promise 实例。调用者无法知道这些 Promise 实例何时解析。
解决这个问题的方法是为每个异步请求都创建一个单独的 async 函数。在这个过程中,父函数使用 Promise.all 来等待所有的异步请求完成。
-- -------------------- ---- ------- ----- -------- ---------------------------------- ----- - ------ -------------------------------------- - ------- ------- ----- ---------------- -------- ---------------- --- -------- - --------------- ------------------ - --- - ----- -------- ---------------------------- - ----- ----- - ----- -------------------- -------------- -- ----------------- ----- -------------------- - -------------- -- ------------------------ --------------- ------- ----- ---------------------------------- -
在这个重构后的代码中,我们创建了一个带有一个 user 参数的函数 sendNotification
。然后,我们使用 users.map
迭代用户列表,并为每个用户调用 sendNotification
。在这个过程中返回的 Promise 每个都通过 Promise.all
方法来等待所有请求成功和失败。
这个代码结构更加清晰,易于阅读和维护。在这个过程中,我们也不会创建过度的 Promise 实例,而是使用类似 Promise.all
这样的工具来等待所有异步请求完成。
结论
async/await 是 JavaScript 异步编程新的标准,它提供了比传统的回调方式和 Promise 更加易用的编程风格。让我们以更清晰的方式表达异步流程,并允许我们在函数中使用 try-catch 来处理错误。
在使用 asyn/await 时,我们需要将异步操作划分得非常微粒化,在组合这些小的、可重用的异步函数进行使用。这样的做法可以使我们的代码更具一致性、可维护性和适应性。
使用async/await编写异步操作的代码不仅更加易读、可维护性和适应性,而且还有助于调试和处理错误(error handling)。异步编程失败时对应的Stack Trace打印错误对于调试非常友好。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/67455e46c1a23897ea92d833