Promise在JavaScript中的工作原理——全面的新手指南

发布时间 2023-06-14 14:22:48作者: 晓风晓浪

JavaScript 具有执行异步(或异步)指令的能力。这些指令在后台运行,直到它们完成处理。

异步指令不会阻止 JavaScript 引擎主动接受和处理更多的指令。这就是 JavaScript 本质上是非阻塞的原因。

JavaScript 中有一些异步特性,其中之一就是Promises。要使用 promises,您必须采用一种特殊的语法,使编写异步指令更有条理。使用 promises 是每个 JavaScript 开发人员都应该学习的一项非常有用的技能。

本文是 JavaScript 中 promises 的深入指南。您将了解为什么 JavaScript 有 promise,什么是 promise,以及如何使用它。您还将学习如何使用 async/await(一种源自 promises 的特性)以及什么是作业队列。

以下是我们将涵盖的主题:

  1. 你为什么要关心承诺?

  2. 什么是承诺?

  3. 如何在 JavaScript 中创建承诺

  4. 如何将回调附加到承诺

  5. 如何处理承诺中的错误

  6. 如何一次处理多个 promise

  7. 什么是异步/等待语法?

  8. 如何在 JavaScript 中创建异步函数

  9. 如何使用 await 关键字

  10. 如何处理异步/等待中的错误

  11. 什么是作业队列?

本指南有望成为一本有趣且富有洞察力的读物。:) 它适用于希望更好地编写 JavaScript 异步指令的任何人,从而正确利用该语言所提供的功能。有了所有这些,让我们开始吧。

(更多优质内容:java567.com)

先决条件

为了跟随材料并掌握它,这里有一些你应该具备的东西:

  • JavaScript 基础知识

  • 了解 JavaScript 如何处理异步操作

了解这些主题将帮助您正确理解您将要学习的内容。如果你没有先决条件,你可以去学习它们并返回。本文将在此处使用这些主题中的一些概念。

为什么要关心 Promise?

Promises 并不总是 JavaScript 的一部分。回调与异步函数一起工作以产生过去所需的结果。回调是作为异步函数参数的任何函数,异步函数调用它来完成其操作。

要调用异步函数,您必须像这样将回调作为参数传递:

 function callback(result) {
   // Use the result from the Async operation
 }
 
 randomAsyncOperation((response) => callback(response));

但是回调有一个很大的问题。演示问题可以使理解更容易。

假设您有一个异步函数可以在互联网上的某个地方获取数据。这个函数应该接受两个回调,successCallback和failureCallback。

successCallback如果操作成功并且程序找到了适当的资源,则 将运行。但是failureCallback如果操作不成功并且找不到资源,它将运行。

 function SuccessCallback(result) {
   console.log("Resource found", result);
 }
 
 function failureCallback(error) {
   console.error("Ooops. Something went wrong", error);
 }

要运行异步函数,您必须将两个回调函数作为参数传递:

 fetchResource(url, successCallback, failureCallback)

这里,url是一个代表资源位置的变量。

这段代码现在可以顺利运行。您已经处理了函数可能遇到的两种可能情况。您有成功操作的回调和失败操作的回调。

现在假设您要执行许多其他提取操作,但每个操作都必须成功才能运行下一个操作。如果您需要的数据必须按特定顺序出现并且不能分散,这将很有用。

例如,如果下一个操作的结果取决于前一个操作的结果,您可能会遇到这种情况。

在这种情况下,您的成功回调将有自己的成功回调,这很重要,因为如果结果进来,您需要使用它们。

 fetchResource(
   url,
   function (result) {
     // Do something with the result
     fetchResource(
       newUrl,
       function (result) {
         // Do something with the new result
         fetchResource(
           anotherUrl,
           function (result) {
             // Do something with the new result
          },
           failureCallback
        );
      },
       failureCallback
    );
  },
   failureCallback
 );

从示例中,您可能会注意到出现了复杂情况。您必须在failureCallback每次调用异步函数时重复嵌套成功回调。

这些嵌套的回调导致了“厄运回调金字塔”或回调地狱,它很快就会变成一场噩梦。有没有更有效的方法来处理这种情况?

JavaScript 引入了 Promises 作为ES6(ES2015)的一部分来解决这个问题。它简化了回调的使用,并提供了更好的语法,您很快就会看到。现在,Promise 是开发人员在 JavaScript 中使用的大多数现代异步操作的基础。

什么是承诺?

图片来源: https: //gifer.com

承诺是对将来会发生某事的保证或保证。一个人可以向另一个人承诺一个特定的结果或结果。承诺不限于个人,政府和组织也可以做出承诺。你之前可能已经做出了承诺。

这种保证(承诺)带来两种可能的结果——实现或失败。承诺与表明它已实现的结果相关联。如果那个结果没有发生,那么承诺就失败了。最后的承诺必须具有这些结果之一。

在 JavaScript 中,Promise 是一个对象,它将在未来某个时间产生一个单一的值。如果承诺成功,它将产生一个已解决的值,但如果出现问题,它将产生承诺失败的原因。这里的可能结果类似于现实生活中的承诺。

JavaScript 承诺可以处于三种可能状态之一。这些状态表示承诺的进度。他们是:

  • pending:这是已定义承诺的默认状态

  • fulfilled:这是承诺成功的状态

  • rejected:这是一个失败的承诺的状态

一个 promise 从pending到fulfilled,或者从pending到rejected —— 'fulfilled' 和 'rejected' 表示一个 promise 的结束。

从现在开始,本文将把“承诺”称为 JavaScript 对象。

如何在 JavaScript 中创建一个 Promise

要创建一个承诺,您需要使用Promise构造函数创建一个实例对象。构造Promise函数接受一个参数。该参数是一个函数,用于定义何时解析新承诺,以及何时拒绝它。

 const promise = new Promise((resolve, reject) => {
   // Condition to resolve or reject the promise
 });

例如,假设您希望承诺在两秒超时后解决。您可以通过将其写入构造函数的参数来实现。

 const promise = new Promise((resolve, reject) => {
   setTimeout(() => resolve("Done!"), 2000);
 });

在 promises 中,resolve是一个带有可选参数的函数,表示已解析的值。此外,reject是一个带有可选参数的函数,表示承诺失败的原因。在上面的示例中,promise 的解析值是 string 'Done!'。

这是另一个示例,显示如何根据您设置的条件解决或拒绝承诺。在此示例中,承诺的结果基于程序生成的随机数。

 const promise = new Promise((resolve, reject) => {
   const num = Math.random();
   if (num >= 0.5) {
     resolve("Promise is fulfilled!");
  } else {
     reject("Promise failed!");
  }
 });

从这些示例中,您可以看到您可以控制何时解决或拒绝您的承诺,并将其与特定条件联系起来。至此,您已经了解了如何在 JavaScript 中创建承诺。

如何将回调附加到 Promise

要为承诺创建回调,您需要使用.then()方法。此方法接受两个回调函数。如果 promise 得到解决,则第一个函数运行,而如果 promise 被拒绝,则第二个函数运行。

 const promise = new Promise((resolve, reject) => {
   const num = Math.random();
   if (num >= 0.5) {
     resolve("Promise is fulfilled!");
  } else {
     reject("Promise failed!");
  }
 });
 
 function handleResolve(value) {
   console.log(value);
 }
 
 function handleReject(reason) {
   console.error(reason);
 }
 
 promise.then(handleResolve, handleReject);
 // Promise is fulfilled!
 // or
 // Promise failed!

这是处理您承诺的可能结果的方式。您的承诺中任何未处理的错误最终都会使它们处于拒绝状态,但已处理的错误会使操作返回已履行的承诺。

可以创建一个立即解析的承诺,然后使用该.then()方法附加一个回调。你也可以用同样的方式创建一个立即被拒绝的承诺。

 Promise.resolve("Successful").then((result) => console.log(result));
 // Successful
 
 Promise.reject("Not successful").then((result) => console.log(result));
 // Error: Uncaught (in promise)

被拒绝的承诺中的错误是因为您需要定义一个单独的回调来处理被拒绝的承诺。

 Promise.reject("Not successful").then(
  () => {
     /*Empty Callback if Promise is fulfilled*/
  },
  (reason) => console.error(reason)
 );
 // Not Successful

现在您已经正确处理了被拒绝的结果。

Promises 使得链接异步指令变得异常容易。当您使用该方法处理承诺时.then(),该操作总是返回另一个承诺。通过采用这种方法,您可以消除前面提到的“厄运回调金字塔”。

考虑之前导致金字塔结构的代码:

 fetchResource(
   url,
   function (result) {
     // Do something with the result
     fetchResource(
       newUrl,
       function (result) {
         // Do something with the new result
         fetchResource(
           anotherUrl,
           function (result) {
             // Do something with the new result
          },
           failureCallback
        );
      },
       failureCallback
    );
  },
   failureCallback
 );

但是,因为.then()返回另一个承诺,所以这就是如何用承诺编写上面相同的指令:

 fetchResource(url)
  .then(handleResult, failureCallback)
  .then(handleNewResult, failureCallback)
  .then(handleAnotherResult, failureCallback);

如您所见,调用 promises 不需要嵌套语法。您甚至可以消除重复项failureCallback以使代码更整洁,这是本文接下来的部分将探讨的内容。

如何处理 Promise 中的错误

要处理 Promises 中的错误,请使用.catch()方法。如果您的任何承诺出现任何问题,此方法可以捕获该错误的原因。

 Promise.reject(new Error()).catch((reason) => console.error(reason));
 // Error

这一次在我们的示例中,错误输出不再是“未捕获”的,因为.catch().

.catch()您还可以在承诺链中使用该方法。它捕获它在链中遇到的第一个错误。

例如,按照fetchResource()上一节示例中的功能重构承诺链。这就是您如何停止代码中的错误回调重复。

 fetchResource(url)
  .then(handleResult)
  .then(handleNewResult)
  .then(handleAnotherResult)
  .catch(failureCallback);

在继续进行进一步的异步操作之前,您还可以使用它.catch()来检查一组 promise 中的错误。

 fetchResource(url)
  .then(handleResult)
  .then(handleNewResult)
  .catch(failureCallback)
   // Check for Errors in the above group of promises before proceeding
  .then(handleAnotherResult);

该.catch()方法无需嵌套错误回调函数即可解决承诺中的任何错误。

要将异步操作链接到承诺,而不管承诺是否已解决,请使用该.finally()方法。该.then()方法是您如何处理承诺的结果,为解决和拒绝编写单独的条件。.catch()仅在出现错误时运行。但有时您可能希望无论先前的承诺发生什么情况,操作都会运行。

Usingfinally()有助于防止.then()和中可能出现的代码重复.catch()。它用于无论是否有错误都必须运行的操作。

 fetchResource(url)
  .then(handleResult)
  .then(handleNewResult)
  .finally(onFinallyHandle);

该finally()方法在实际应用中有一些用例。如果您想对 promise 发起的活动执行清理操作,这一点很重要。前端 Web 应用程序的另一个用例是更新用户界面,例如停止加载微调器。

如何一次处理多个 Promise

一次可以运行多个承诺。到目前为止,您看到的所有示例都是针对一个接一个运行的承诺。

在前面的例子中,promises 的运行类似于同步代码,因为它们等待前一个代码被解决或拒绝。但是你可以有多个并行运行的承诺。

以下是可以帮助我们实现这一目标的可用方法:

  • Promise.all()

  • Promise.race()

  • Promise.any()

  • Promise.allSettled()

在本文的这一部分,我们将回顾这些方法。

方法Promise.all()_

Promise.all()接受一组承诺作为参数,但返回单个承诺作为输出。如果输入数组中的所有承诺都得到满足,它返回的单个承诺将解析为一个值数组。数组Promise.all()resolve with 将包含输入数组中各个承诺的解析值。

 const promise1 = Promise.resolve(`First Promise's Value`);
 const promise2 = new Promise((resolve) =>
   setTimeout(resolve, 3000, `Second Promise's Value`)
 );
 const promise3 = new Promise((resolve) =>
   setTimeout(resolve, 2000, `Third Promise's Value`)
 );
 
 Promise.all([promise1, promise2, promise3]);
 
 // Output on the console
 
 // *Promise {<fulfilled>: Array(3)}*
 
 Promise.all([promise1, promise2, promise3]).then((values) => {
   values.forEach((value) => console.log(value));
 });
 
 // Output on the console
 
 // First Promise's Value
 // Second Promise's Value
 // Third Promise's Value

如果输入数组中至少有一个承诺没有解决,Promise.all()将返回一个被拒绝的承诺并说明原因。拒绝的原因与输入数组中第一个被拒绝的承诺相同。

 const promise1 = Promise.resolve(`First Promise's Value`);
 const promise2 = new Promise((resolve, reject) =>
   setTimeout(reject, 3000, `First reason for rejection`)
 );
 const promise3 = new Promise((resolve, reject) =>
   setTimeout(reject, 2000, `Second reason for rejection`)
 );
 
 Promise.all([promise1, promise2, promise3]);
 
 // Output on the console
 
 // *Promise {<rejected>: "First reason for rejection"}*

Promise.all()将在返回值之前运行所有输入承诺。但它不会一个接一个地运行承诺——而是同时运行它们。

这就是为什么返回一个值所花费的总时间Promise.all()大约是数组中最长的承诺完成所花费的时间。

 

尽管如此,它必须在返回任何内容之前完成所有的承诺。

方法Promise.race()_

Promise.race()接受一组承诺作为参数,并返回一个承诺作为输出。它返回的单个承诺是完成运行的最快承诺——无论是否已解决。这意味着Promise.race()将返回承诺数组中执行时间最短的承诺。

 const promise1 = new Promise((resolve) =>
   setTimeout(resolve, 3000, `First Promise's Value`)
 );
 const promise2 = new Promise((resolve) =>
   setTimeout(resolve, 2000, `Second Promise's Value`)
 );
 const promise3 = Promise.resolve(`Third Promise's Value`);
 
 Promise.race([promise1, promise2, promise3]);
 
 // Output on the console
 
 // *Promise {<fulfilled>: "Third Promise's Value"}*

在上面的示例中,因为promise3是一个在创建时解决的承诺,所以Promise.race()它以最快的速度返回。就像Promise本文在本节中讨论的其他方法一样,它并行运行承诺,而不是一个接一个地运行。

如果执行时间最短的 promise 碰巧被拒绝了,Promise.race()则返回一个被拒绝的 promise 以及最快的 promise 被拒绝的原因。

 const promise1 = Promise.reject(`Reason for rejection`);
 const promise2 = new Promise((resolve) =>
   setTimeout(resolve, 3000, `First resolved Promise`)
 );
 const promise3 = new Promise((resolve) =>
   setTimeout(resolve, 2000, `Second resolved Promise`)
 );
 
 Promise.race([promise1, promise2, promise3]);
 
 // Output on the console
 
 // *Promise {<rejected>: "Reason for rejection"}*

 

Promise.race()对于运行一系列异步操作但只需要最快执行操作的结果很有用。

方法Promise.any()_

Promise.any()接受 Promise 数组作为参数,但返回单个 Promise 作为输出。它返回的单个承诺是输入数组中第一个已解决的承诺。此方法等待数组中的任何承诺得到解决,并立即将其作为输出返回。

 const promise1 = new Promise((resolve) =>
   setTimeout(resolve, 3000, `First Promise's Value`)
 );
 const promise2 = new Promise((resolve) =>
   setTimeout(resolve, 2000, `Second Promise's Value`)
 );
 const promise3 = Promise.reject(`Third Promise's Value`);
 
 Promise.any([promise1, promise2, promise3]);
 
 // Output on the console
 
 // *Promise {<fulfilled>: "Second Promise's Value"}*

从上面的例子来看,promise13秒后会解析,promise22秒后会解析,然后promise3立即拒绝。因为Promise.any()正在寻找第一个成功的承诺,所以它返回promise2。promise1有点晚了,所以它落在后面了。

如果数组中的任何承诺都没有得到解决,Promise.any()则返回一个被拒绝的承诺。这个被拒绝的承诺包含一个 JavaScript 原因数组,其中每个原因都与输入数组中的一个承诺相对应。

 const promise1 = new Promise((resolve, reject) =>
   setTimeout(reject, 3000, `First rejection reason`)
 );
 const promise2 = new Promise((resolve, reject) =>
   setTimeout(reject, 2000, `Second rejection reason`)
 );
 const promise3 = Promise.reject(`Third rejection reason`);
 
 Promise.any([promise1, promise2, promise3]);
 
 // Output on the console
 
 // *Promise {<rejected>: Aggregate Error: All Promises were rejected}*
 
 Promise.any([promise1, promise2, promise3]).catch(({ errors }) =>
   console.log(errors)
 );
 
 // Output on the console
 
 // *(3) ["First* rejection reason*", "Second* rejection reason*", "Third* rejection reason*"]*

此方法对于异步操作很有用,其中最快的成功承诺就是您所需要的。Promise.any()并且Promise.race()是相似的,除了Promise.any()将返回最快完成和解决的承诺,而Promise.race()将返回最快完成的承诺并且不关心它是否已解决。

 

方法Promise.allSettled()_

Promise.allSettled()随着ES2020的发布,成为了 JavaScript Promise 的一个特性。它像本文在本节中讨论的其他 promise 方法一样并行处理 promise。

Promise.allSettled()有助于编写更高效的异步代码,因为它显示数组中所有承诺的结果,无论状态如何——已解决或已拒绝。

Promise.allSettled()接受一组承诺作为参数,并返回一个承诺作为输出。

它返回的单个承诺将始终解决或在所有输入承诺都得到解决后进入“已完成”状态。它不关心输入数组中是否有任何个别承诺被拒绝。数组Promise.all()resolve with 将包含输入数组中承诺的解析值或拒绝原因。

 const promise1 = new Promise((resolve) =>
   setTimeout(resolve, 3000, `First Promise's Value`)
 );
 const promise2 = new Promise((resolve) =>
   setTimeout(resolve, 2000, `Second Promise's Value`)
 );
 const promise3 = Promise.reject(`Third Promise's Value`);
 
 Promise.allSettled([promise1, promise2, promise3]);
 
 // Output on the console
 
 // *Promise {<fulfilled>: Array(3)}*
 
 Promise.allSettled([promise1, promise2, promise3]).then(console.log);
 
 // Output on the console
 
 /*
 (3) [{…}, {…}, {…}]
 0: {status: 'fulfilled', value: "First Promise's Value"}
 1: {status: 'fulfilled', value: "Second Promise's Value"}
 2: {status: 'rejected', reason: "Third Promise's Value"}
 */

从上面的示例中,您可以看到即使promise3在创建时拒绝,Promise.allSettled()仍然返回一个“已完成”的承诺。即使输入数组中的所有承诺都被拒绝,它也会这样做。

Promise.allSettled()类似于Promise.all()他们所有的输入承诺必须在他们返回的承诺有一个稳定的状态之前解决——完成或拒绝。

不同的是,Promise.all()只有在输入中的所有承诺都被解决时才能成功,而不Promise.allSettled()关心输入承诺的状态。

 

使用此方法可以让您大致了解所有承诺的执行情况、已解决的承诺和被拒绝的承诺。它提供有关您传递给它的所有承诺的完整信息,并允许您独立检查它们——一个结果不会影响该方法返回的承诺的状态。

什么是异步/等待语法?

随着ES8(ES2017)的发布,Async/await 语法成为 JavaScript 的一个特性。它建立在 promises 之上,您可以将其视为 promises 的替代语法。

async/await消除了 promises 语法中常见的链接,最终使异步代码看起来更加同步。

Promises 是避免前面讨论的“厄运回调金字塔”的绝佳方式,但 async/await 使异步代码更进一步。使用 async/await,代码更容易遵循和维护。它的出现是为了提高异步操作的代码可读性。这是使用承诺的现代方式。

如何在 JavaScript 中创建异步函数

 async`是用于创建函数的 JavaScript 关键字。this 关键字帮助创建的函数将始终返回一个承诺。要使用它,请在声明函数时放在关键字`async`之前。`function
 async function example() {
  // Return a value
 }
 
 example()
 
 // Output on the console
 
 // *Promise {<fulfilled>: undefined}*

从代码示例中,您可以看到该函数返回一个带有 value 的 promise undefined。这是因为async函数返回的任何内容都将是结果承诺的解析值。在这种情况下,该函数不返回任何内容,因此undefined.

 async function example() {
   return "Feels good to be an async function";
 }
 
 example();
 
 // Output on the console
 
 // *Promise {<fulfilled>: "Feels good to be an async function"}*

在上面的例子中,函数返回一个字符串,它成为结果承诺的解析值。这就是创建函数的方法async。

如何使用等待关键字

要使用await关键字,请将其放在承诺之前。async它是函数暂停执行直到该承诺得到解决的指示器。

它类似于.then()确保承诺在继续之前被“履行”或“拒绝”的方法。请注意,您只能在函数await内部使用关键字async。

您可以重复await异步操作,而不是像.then()前面的文章所教导的那样将 promise 与 链接起来,从而使您的代码更清晰、更易于阅读。

 const timerPromise = (message) =>
   new Promise((resolve) => setTimeout(resolve, 3000, message));
 
 async function asyncFunc() {
   const result = await timerPromise("promise finished!");
   console.log(result);
 }
 
 // Output on the Console after 3 seconds
 
 // promise finished!

await在 promise 之前使用关键字将产生该 promise 的解析值。const result = await promise('promise finished!')从where result变成一个字符串而不是一个新的 promise 的行可以明显看出。这不同于.then()which always returns a new promise。

使用await,您可以打破任何承诺链,并获取它们的 resolve 值。以下示例使用该fetch()函数(它是一个承诺)来展示如何使用 async/await 消除链接。

 // With chaining
 fetch("jsonplaceholder.typicode.com/users")
  .then((response) => response.json())
  .then((result) => console.log(result));
 
 // Output on the console
 
 // Array(10) [...]
 
 // Without chaining
 async function fetchResource(url) {
   const response = await fetch(url);
   const result = await response.json();
   console.log(result);
 }
 fetchResource("jsonplaceholder.typicode.com/users");
 
 // Output on the console
 
 // Array(10) [...]

最后,它归结为偏好和选择。如果您更喜欢链接语法,那就去吧。如果你希望你的代码看起来是同步的并且想使用 async/await,那也很好。

您还可以同时使用这两种语法,将 promises 链接在一个异步函数中。这完全取决于您想要实现的目标和您喜欢的风格。

如何处理 Async/Await 中的错误

就像使用普通的 promise 语法一样,您可以使用 async/await 正确捕获错误。正确处理异步调用中的错误对于跟踪错误极为重要。使用 try/catch 块来执行此操作。

try是包装代码块的 JavaScript 关键字。当该代码块运行时,try检查错误。没有错误可以逃脱 try 块。try在函数内部使用async。

块内的第一个错误try会停止执行该块中的其他指令,try然后将错误值传递给catch块。该catch块类似于.catch()承诺。就像 promise 方法一样,它是一个错误的函数。

 async function fetchResource(url) {
   try {
     const response = await fetch(url);
     const result = await response.json();
     console.log(result);
  } catch (error) {
     console.error(error);
  }
 }

在这个例子中,catch关键字有一个错误,记录到控制台。带有未捕获错误的 settled promise 会导致 rejected promise。确保将代码包装在 try/catch 块中,以便更好地控制程序中的失败和错误。

此外,就像.finally()承诺的方法一样,您可以finally在异步函数中使用块。这个关键字后面的大括号环绕着一段代码,无论是否有错误都会运行。

 async function fetchResource(url) {
   try {
     const response = await fetch(url);
     const result = await response.json();
     console.log(result);
  } catch (error) {
     console.error(error);
  } finally {
     console.log("Operation finished!");
  }
 }

块的使用finally与方法的使用类似.finally()。这只是证明使用异步函数是最近使用 promises 的方法。

什么是作业队列?

作业队列——也称为微任务队列——是 JavaScript 中需要注意的一个重要概念。它最初不是 JavaScript 运行时的组件,但是当 promises 成为 JavaScript 的一部分时,对它的需求就来了。

考虑以下代码示例:

 Promise.resolve("This is a resolved value").then(console.log);
 setTimeout(console.log, 0, "This is a value after the timeout");
 console.log("This is a normal log");

这里的第一行是一个自动解析的承诺,然后在控制台上记录值。第二行是超时设置为 0 毫秒,这意味着它应该是即时的。超时接受一个回调函数,该函数将一个值记录到控制台。第三行是正常的控制台日志。

当您运行该程序时,您能猜出这些日志出现的顺序吗?让我们找出来。

 // Output on the console
 /*
 This is a normal log
 This is a resolved value
 --
 undefined
 --
 This is a value after the timeout
 */

这是一个有趣的输出。第一条日志来自console.log. 它不是那么混乱,因为console.log()它不是异步操作。JavaScript 引擎会在程序启动后立即主动运行每条同步指令。

第二行可能有点令人费解。它记录了 promise 的解析值。为什么接下来是 promise 的输出?好吧,简单的答案是 promise 比 JavaScript 中的任何其他异步实现都要快。但这还不是全部。

在 JavaScript 运行时,事件循环处理异步操作。它只能在调用栈为空时调用异步指令的回调函数。Resolving a promise 是一个异步操作,在普通日志之后是可以理解的。但是为什么它出现在setTimeout()指令之前呢?

JavaScript 运行时实际上有这两个队列——回调(或宏任务)队列和作业(或微任务)队列。在事件循环开始调用回调队列中的函数之前不久,它会调用作业队列中的所有指令。promise 的回调保留在作业队列中,因此事件循环首先调用它。这就是为什么 promises 返回值比任何其他异步实现更快的原因。

除了 promises 之外,Job Queue 对于其他一些指令也很有用。但是,这超出了本材料的范围。如果你很好奇,那么你可以在这里阅读更多关于作业队列的信息。

程序处理完作业队列后立即返回。从上面的代码示例中,它返回带有undefined. 之后,事件循环移至回调队列并执行那里的指令。

图片来源:Medium上的Saravanakumar

setTimeout()无论计时器有多短,回调都会一直移动到回调队列。在示例中,即使计时器设置为 0 毫秒,它也会最后记录。

至此,我希望您能理解为什么示例代码会产生该输出,以及回调队列和微任务队列之间的区别。

结论

这是对承诺和异步操作的深入研究。在本文中,您了解了 JavaScript 中的承诺是如何产生的、它们是什么以及如何创建它们。

您还学习了如何将回调附加到承诺、如何捕获承诺中的错误以及如何同时运行多个承诺。

我们还研究了构建在 promises 之上的 async/await 语法。您了解了它们何时成为 JavaScript 的一项功能、如何创建异步函数以及如何使用 await 关键字。

您还学习了如何使用语法处理错误。最后,文章解释了什么是 Job Queue。

如果您没有一下子理解所有内容,请随时回来。学习和掌握 JavaScript promises 可能需要时间,但任何 JavaScript 开发人员都将从了解 promises 的工作原理中获益匪浅。在为您的应用程序编写专业的异步代码时,它让您有更多的控制权。

祝你构建下一个项目好运。

(更多优质内容:java567.com)