发布于 2026-01-06 5 阅读
0

异步 JavaScript 入门

异步 JavaScript 入门

这篇博文最初发表在Tes Engineering 博客上

以下是对使用异步 JavaScript 的一些基本原理的简要回顾,并附有一些实际示例。

为什么我又需要使用异步代码?

JavaScript 本质上是同步的。每一行代码都按照它在代码中出现的顺序执行。它也是单线程的,一次只能执行一个命令。

如果某个操作需要花费一些时间才能完成,那么等待它完成实际上就相当于阻塞了整个流程。这种情况常见于调用 API 并等待响应,或者查询数据库并等待结果等场景。最终,这会导致用户体验缓慢且令人沮丧,甚至可能导致用户离开您的网站。

异步编程提供了一种绕过 JavaScript 同步单线程特性的方法,使我们能够在后台执行代码。

承诺

Promise 使 JavaScript 能够实现异步编程。Promise 会创建一个替代值来代替异步任务的等待值,并允许异步方法像同步方法一样返回值。异步方法不会立即返回最终值,而是返回一个 Promise,承诺在未来的某个时间点提供该值。

我们来看几种常见的 Promise 实现方式。示例代码摘自我正在开发的一个名为“安全仪表盘”的玩具项目,感兴趣的朋友可以点击这里了解更多信息。

连锁承诺

const fetchLatestDevToNewsPromiseChaining = () => {
  return fetch('https://dev.to/api/articles?per_page=5&tag=security')
    .then(response => response.json())
    .then(latestArticles => keyDevToInfo(latestArticles))
    .catch(err)
};
Enter fullscreen mode Exit fullscreen mode

JavaScript 内置的Fetch API返回一个 promise 对象,然后我们可以将 promise 方法“链式”地应用到该对象上,以便处理响应。

.then()它将回调函数的返回值传递给后续函数.then(),同时.catch()处理被拒绝的 Promise。我们可以通过添加更多 Promise 方法来继续“链式”处理结果。

异步/等待

const fetchLatestDevToNewsAsyncAwait = async () => {
  try {
    const response = await fetch("https://dev.to/api/articles?per_page=5&tag=security")
    const latestArticles = await response.json()
    return keyDevToInfo(latestArticles)
  } catch (err) {
    return err
  }
}
Enter fullscreen mode Exit fullscreen mode

另一种常用的方法是使用async/await。我们在函数声明中使用关键字async,然后await在向 API 发出请求之前立即使用该关键字。这样,我们就无需使用 Promise 方法来处理响应,而是可以像编写其他同步 JavaScript 代码一样,直接编写后续的处理代码。

由于这里我们没有使用 promise 方法,所以我们应该使用 try / catch 块来处理任何被拒绝的 promise。

你会注意到,在这两种情况下,我们都不需要实际创建 Promise 对象:大多数用于协助向 API 发出请求的库默认都会返回一个 Promise 对象。因此,很少需要使用Promise 构造函数

处理承诺

无论你是使用链式 Promise 还是 async/await 来编写异步 JavaScript,都会返回一个 Promise,因此在调用包装异步代码的函数时,我们也需要解决该 Promise 以取回其值。

JavaScript 内置了一些可迭代方法来处理这些问题,以下是一些非常方便的用于处理多个 Promise 结果的方法:

承诺。

Promise.all([fetchLatestDevToNewsPromiseChaining(), fetchLatestDevToNewsAsyncAwait()])
  .then(([chained, async]) => {
    createFile([...chained, ...async])
  })

Enter fullscreen mode Exit fullscreen mode

对于相互依赖的异步任务,`Promise.all`是一个不错的选择。如果其中一个 Promise 被拒绝,它会立即返回该 Promise 的值。如果所有 Promise 都已解决,则会按照 Promise 执行的顺序返回已解决 Promise 的值。

如果你不知道传入的 Promise 数组的大小,这可能不是一个好选择,因为它可能会导致并发问题。

承诺。一切就绪

Promise.allSettled([fetchLatestDevToNewsPromiseChaining(), fetchLatestDevToNewsAsyncAwait()])
  .then(([chained, async]) => {
    createFile([...chained, ...async])
  })
Enter fullscreen mode Exit fullscreen mode

`Promise.allSettled`适用于彼此不依赖的异步任务,因此无需立即拒绝。它与 `Promise.all` 非常相似,区别在于,无论 Promise 是被拒绝还是被解决,最终你都会得到所有 Promise 的结果。

承诺.竞赛

Promise.race([fetchLatestDevToNewsPromiseChaining(), fetchLatestDevToNewsAsyncAwait()])
  .then(([chained, async]) => {
    createFile([...chained, ...async])
  })
Enter fullscreen mode Exit fullscreen mode

当你想获取第一个待解决或待拒绝的 Promise 的结果时, `Promise.race`非常有用。一旦 Promise 有了结果,它就会立即返回该结果——因此,它并不适合用于这段代码。

所以……我应该使用链式 Promise 还是 async/await?

我们已经了解了 JavaScript 中处理异步代码的两种常见方法:链式 Promise 和 async/await。

这两种方法有什么区别?区别不大:选择哪一种更多是风格偏好。

使用 async/await 可以让代码更易读、更易于理解,因为它更像同步代码。同样地,如果需要执行多个后续操作,在代码中使用多个链式 Promise 可能会使代码更难理解。

另一方面,也可以说,如果这是一个简单的操作,后续操作很少,那么内置.catch()方法的表述就非常清晰。

无论你采取哪种方法,都应该庆幸自己可以选择避免反复回访的痛苦

文章来源:https://dev.to/charlottebrf_99/asynchronous-javascript-101-4cp2