作为一种单线程语言,JavaScript 一直依赖异步编程来处理耗时的任务而不阻塞代码执行。多年来,JavaScript 中处理异步性的方法已经发生了显着的发展,变得更易读、更易于管理、更易于推理。让我带您了解异步 JavaScript 的历史,从回调到 Promise,再到 async/await。
在 JavaScript 的早期,在广泛采用回调之前,大多数 JavaScript 代码都是同步编写的。同步代码意味着每个操作都以阻塞方式依次执行。当遇到长时间运行的操作时,整个脚本的执行将暂停,直到该操作完成。
想象一下,您在火车站售票柜台,只有一名售票员。您申请门票,售票员开始处理您的请求。在同步模型中,您必须在柜台等待,直到售票员处理完您的请求并将门票交给您。在此期间,无法为其他人提供服务,整个售票柜台都被堵住了。
这是同步 JavaScript 代码的示例:
console.log("Before operation"); // Simulating a long-running operation for (let i = 0; i < 1000000000; i++) { // Performing some computation } console.log("After operation");
在此代码中,console.log 语句将按顺序执行,长时间运行的操作(for 循环)将阻止脚本的执行,直到完成。 “操作后”消息仅在循环结束后才会被记录。
虽然同步代码易于理解和推理,但它会带来一些问题,特别是在处理耗时的操作时:
为了克服同步代码的限制并提供更好的用户体验,引入了异步编程技术。异步编程允许在后台执行长时间运行的操作,而不会阻止其余代码的执行,这就是回调的引入方式。
回调是处理异步操作的主要方式。回调只是作为参数传递给另一个函数的函数,稍后在异步操作完成后执行。
想象一下您想购买一张火车票。您前往火车站的售票柜台索取特定目的地的车票。售票员接受您的请求并要求您稍候,他们会检查火车上的空座情况。您向他们提供您的联系信息并在等候区等候。一旦售票员处理了您的请求并且有空位,他们就会叫出您的名字,让您知道您的票已准备好领取。在这个类比中,您的联系信息就是回调 - 售票员在异步任务(检查座位可用性和出票)完成时通知您的一种方式。
以下是与 JavaScript 中的回调的类比:
在回调方法中,您提供一个函数(回调),异步操作完成后将调用该函数。异步函数执行其任务,然后使用结果或错误调用回调,从而允许您的代码处理异步操作的结果。
以下是在 Node.js 中使用回调进行 API 调用的示例:
console.log("Before operation"); // Simulating a long-running operation for (let i = 0; i < 1000000000; i++) { // Performing some computation } console.log("After operation");
在此示例中,我们有一个模拟 API 调用的 fetchData 函数。它接受一个 url 参数和一个回调函数作为参数。在函数内部,我们使用 setTimeout 来模拟调用回调函数之前 1000 毫秒(1 秒)的延迟。
回调函数遵循常见约定,接受错误作为第一个参数 (err),接受数据作为第二个参数 (data)。在此示例中,我们通过将错误设置为 null 并提供示例数据对象来模拟成功的 API 调用。
要使用 fetchData 函数,我们使用 URL 和回调函数来调用它。在回调函数中,我们首先通过检查 err 参数来检查是否发生错误。如果存在错误,我们使用 console.error 将其记录到控制台并返回以停止进一步执行。
如果没有发生错误,我们使用console.log将接收到的数据记录到控制台。
当您运行此代码时,它将模拟异步 API 调用。延迟1秒后,将调用回调函数,并将结果记录到控制台:
{ id: 1, name: 'John Doe' }
此示例演示了如何使用回调来处理异步 API 调用。回调函数作为参数传递给异步函数 (fetchData),并在异步操作完成后调用它,无论是出现错误还是结果数据。
虽然回调完成了工作,但它们有几个缺点:
为了解决回调带来的挑战,ES6 (ECMAScript 2015) 中引入了 Promise。 Promise 代表异步操作的最终完成或失败,并允许您将操作链接在一起。
将承诺想象成一张火车票。当您购买火车票时,该车票代表铁路公司对您能够登上火车并到达目的地的承诺。车票上包含有关火车的信息,例如出发时间、路线和座位号。拿到车票后,就可以等待火车到站,准备上车后,就可以凭车票上车了。
在这个比喻中,火车票就是承诺。它代表异步操作(火车旅程)的最终完成。您保留票(承诺对象),直到火车准备好(异步操作完成)。一旦承诺得到解决(火车到站),您就可以使用车票登上火车(获取解决值)。
以下是与 JavaScript 中的 Promise 的类比:
Promise 提供了一种结构化的方式来处理异步操作,允许您将多个操作链接在一起并以更易于管理的方式处理错误,就像火车票如何帮助您组织和管理火车旅程一样。
这是使用 Promise 进行 API 调用的示例:
console.log("Before operation"); // Simulating a long-running operation for (let i = 0; i < 1000000000; i++) { // Performing some computation } console.log("After operation");
在此代码中,fetchData 函数返回一个承诺。 Promise 构造函数采用一个接受两个参数的函数:resolve 和reject。这些函数用于控制 Promise 的状态。
在 Promise 构造函数中,我们使用 setTimeout 模拟 API 调用,就像前面的示例一样。然而,我们没有调用回调函数,而是使用resolve和reject函数来处理异步结果。
如果发生错误(在本例中,我们通过检查错误变量来模拟错误),我们会调用带有错误的拒绝函数,表明该 Promise 应该被拒绝。
如果没有发生错误,我们将使用数据调用resolve函数,表示应使用接收到的数据来解析promise。
要使用 fetchData 函数,我们将 .then() 和 .catch() 方法链接到函数调用。 .then() 方法用于处理 Promise 的解析值,而 .catch() 方法用于处理可能发生的任何错误。
如果 Promise 成功解析,则会使用解析后的数据调用 .then() 方法,并使用 console.log 将其记录到控制台。
如果发生错误并且 Promise 被拒绝,则会使用 err 对象调用 .catch() 方法,然后我们使用 console.error 将其记录到控制台。
与回调相比,使用 Promise 提供了一种更加结构化和可读的方式来处理异步操作。 Promise 允许您使用 .then() 将多个异步操作链接在一起,并使用 .catch() 以更集中的方式处理错误。
Promise 通过多种方式改进了回调:
但是,Promise 仍然有一些局限性。链接多个 Promise 仍然可能导致深度嵌套的代码,并且语法并不像应有的那么干净。
ES8 (ECMAScript 2017) 中引入的 Async/await 构建在 Promise 之上,并提供了一种看起来更同步的方式来编写异步代码。
使用 async/await,您可以编写外观和行为类似于同步代码的异步代码。这就像有一个私人助理帮你去售票柜台。你只需等待你的助手带着票回来,一旦他们回来,你就可以继续你的旅程了。
这是使用 async/await 进行 API 调用的示例:
console.log("Before operation"); // Simulating a long-running operation for (let i = 0; i < 1000000000; i++) { // Performing some computation } console.log("After operation");
在此代码中,我们有一个名为 fetchData 的异步函数,它采用表示 API 端点的 url 参数。在函数内部,我们使用 try/catch 块来处理 API 请求期间可能发生的任何错误。
我们在 fetch 函数之前使用 wait 关键字来暂停执行,直到 fetch 返回的 Promise 得到解析。这意味着该函数将等到 API 请求完成后再继续下一行。
收到响应后,我们使用await response.json() 将响应正文解析为JSON。这也是一个异步操作,所以我们使用await来等待解析完成。
如果API请求和JSON解析成功,则从fetchData函数返回数据。
如果 API 请求或 JSON 解析过程中发生任何错误,都会被 catch 块捕获。我们使用 console.error 将错误记录到控制台,并使用 throw err 重新抛出错误,将其传播给调用者。
为了使用 fetchData 函数,我们有一个名为 main 的异步函数。在 main 中,我们指定要从中获取数据的 API 端点的 url。
我们使用await fetchData(url)来调用fetchData函数并等待它返回数据。如果 API 请求成功,我们会将收到的数据记录到控制台。
如果 API 请求过程中发生任何错误,则会被 main 函数中的 catch 块捕获。我们使用 console.error 将错误记录到控制台。
最后我们调用main函数开始执行程序。
当您运行此代码时,它将使用 fetch 函数向指定的 URL 发出异步 API 请求。如果请求成功,接收到的数据将记录到控制台。如果发生错误,也会被捕获并记录。
将 async/await 与 fetch 函数结合使用,提供了一种干净且可读的方式来处理异步 API 请求。它允许您编写外观和行为类似于同步代码的异步代码,使其更易于理解和维护。
总之,异步 JavaScript 的演变,从回调到 Promise 再到 async/await,一直是朝着更具可读性、可管理性和可维护性的异步代码迈进的旅程。每一步都建立在前一步的基础上,解决了限制并改善了开发人员体验。
如今,async/await 已被广泛使用,并已成为 JavaScript 中处理异步操作的首选方式。它允许开发人员编写干净、简洁且易于理解的异步代码,使其成为每个 JavaScript 开发人员工具箱中的宝贵工具。
以上是异步 JavaScript - 从回调到异步等待的旅程的详细内容。更多信息请关注PHP中文网其他相关文章!