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

了解 JavaScript 中的 Promise AWS 安全直播!

理解 JavaScript 中的 Promise

AWS 安全直播!


我向你保证,看完这篇文章,你一定会对
JavaScript Promises 有更深入的了解。

我对 JavaScript 一直抱有一种“爱恨交加”的复杂情感。尽管如此,
JavaScript 始终令我着迷。
过去十年我一直从事 Java 和 PHP 的开发工作,相比之下,JavaScript 显得截然不同,却又格外吸引我。我
之前投入到 JavaScript 学习中的时间并不多,
最近一直在努力弥补这方面的不足。

Promises 是我接触到的第一个有趣的话题。我经常
听到人们说 Promises 能让你摆脱回调地狱。虽然这
可能是一个令人欣喜的附加效果,但 Promises 的功能远不止于此,以下是
我目前为止的发现。这篇
文章会比较长,如果你想高亮显示某些部分,可以使用我们的扩展程序:
http://bit.ly/highlights-extension

背景

当你第一次接触 JavaScript 时,可能会感到有些
沮丧。你会听到有人说 JavaScript 是同步
编程语言,而另一些人则认为它是异步的。你还会听到
阻塞代码、非阻塞代码、事件驱动设计模式、事件生命周期、
函数栈、事件队列、冒泡、polyfill、Babel、Angular、ReactJS、Vue.js
以及其他一大堆工具和库。别担心,你不是第一个。这种
情况还有一个专门的术语,叫做“JavaScript 疲劳”。这条推文
很好地概括了这一点。

如果你想了解更多关于 JavaScript 疲劳的信息,可以看看
这篇文章。这篇文章在 Hackernoon 上获得了 4.2 万个赞是有原因的 :)

JavaScript 是一种同步编程语言。但借助回调
函数,我们可以让它像异步编程语言一样工作。

对普通人的承诺

JavaScript 中的 Promise 与你在现实生活中做出的承诺非常相似。
所以,我们先来看看现实生活中的 Promise。

字典中对“承诺”的定义如下:

promise:名词:保证某人会做某事或某件事
会发生。

那么,当有人向你做出承诺时会发生什么呢?

  1. 承诺能让你确信某件事将会完成。至于承诺者是亲自去做,还是委托他人去做,这并不重要。承诺给你的是一种保证,你可以据此制定计划。
  2. 承诺要么遵守,要么违背。
  3. 当承诺得到履行时,你会期望从中获得一些回报。你可以利用承诺的成果来指导你接下来的行动或计划。
  4. 当承诺被违背时,你会想知道为什么做出承诺的人没能履行诺言。一旦你了解了原因并确认承诺已被违背,你就可以计划下一步该怎么做或如何处理此事。
  5. 做出承诺时,我们所拥有的仅仅是保证,无法立即采取行动。我们可以决定并制定在承诺兑现(因此我们有了预期结果)或违背(因此我们知道原因,从而可以制定应急预案)时需要采取的措施。
  6. 有可能你根本收不到做出承诺的人的回复。在这种情况下,你最好设定一个时间期限。比如说,如果做出承诺的人10天后没有回复,我会认为他遇到了什么问题,不会履行承诺。所以,即使15天后他回复了,你也无所谓了,因为你已经另作安排。

JavaScript 中的 Promise

一般来说,学习 JavaScript 时我总是阅读 MDN Web
Docs 上的文档。我认为在所有资源中,它们提供的细节最简洁明了。我阅读
MDN Web
Docs

上的 Promises 页面 ,并尝试编写代码来掌握它。

理解 Promise 包含两个部分:Promise 的创建
Promise 的处理。虽然我们的大部分代码通常都会处理
由其他库创建的 Promise,但全面理解 Promise 肯定会对我们有所帮助。 一旦你过了入门阶段,
理解“Promise 的创建”就同样重要了。

创造承诺

让我们来看一下创建新承诺的标志。

构造函数接受一个名为 `executor` 的函数。该executor函数
接受两个参数resolvereject这两个参数本身也是函数。Promise
通常用于更轻松地处理异步操作或
阻塞代码,例如文件操作、API 调用、数据库调用、IO
调用等。这些异步操作的启动发生在 `
executorexecutor` 函数内部。如果异步操作成功,则Promise的创建者
会调用 `executor` 函数来返回预期结果 。同样,如果出现意外错误,则会 通过调用`executor` 函数来传递错误原因。resolve

reject

既然我们已经知道如何做出承诺,那么为了便于
理解,让我们来做出一个简单的承诺。

var keepsHisWord;
keepsHisWord = true;
promise1 = new Promise(function(resolve, reject) {
  if (keepsHisWord) {
    resolve("The man likes to keep his word");
  } else {
    reject("The man doesnt want to keep his word");
  }
});
console.log(promise1);


每个承诺都有其状态和价值

由于这个 Promise 会立即被解析,我们无法检查
它的初始状态。所以,让我们创建一个需要
一些时间才能解析的新 Promise。最简单的方法是使用setTimeOut
函数。

promise2 = new Promise(function(resolve, reject) {
  setTimeout(function() {
    resolve({
      message: "The man likes to keep his word",
      code: "aManKeepsHisWord"
    });
  }, 10 * 1000);
});
console.log(promise2);

上面的代码创建了一个 Promise,它会在 10
秒后无条件地解析。因此,我们可以持续检查该 Promise 的状态,直到它被解析为止。


在问题解决或被拒绝之前,这是一种承诺状态。

十秒钟过后,Promise 即被解析。`and`PromiseStatus和`or`
PromiseValue都会相应更新。如您所见,我们更新了 `resolve`
函数,使其可以传递 JSON 对象而非简单的字符串。这
只是为了说明我们也可以在该resolve函数中传递其他值。


一个在 10 秒后解析的 Promise,返回一个 JSON 对象。

现在我们来看一个会被拒绝的承诺。
为此,我们只需对承诺 1 稍作修改。

keepsHisWord = false;
promise3 = new Promise(function(resolve, reject) {
  if (keepsHisWord) {
    resolve("The man likes to keep his word");
  } else {
    reject("The man doesn't want to keep his word");
  }
});
console.log(promise3);

由于这将导致未经处理的拒绝,Chrome 浏览器会显示错误信息。
您可以暂时忽略它,我们稍后再处理。

拒绝承诺

我们可以看到,PromisePromiseStatus可以有三个不同的值。当 Promise 创建时,它将处于待处理状态,直到 Promise 变为已处理或已完成状态。当 Promise 处于待处理或已完成状态时,我们称该 Promise 为已完成因此Promise通常 处理状态转换为已完成状态。pending
resolvedrejectedPromiseStatus
pendingPromiseValueundefined
resolvedrejected.resolved
rejectedsettled.

现在我们了解了 Promise 的创建方式,接下来就可以看看如何使用或
处理 Promise 了。这与理解对象本身密不可分Promise

理解承诺对象

根据 MDN 文档

Promise对象表示异步操作的最终完成(或失败)
及其结果值。

Promise对象拥有静态方法,prototype methods静态方法
Promise可以独立应用,而原型方法则prototype methods
需要应用于对象的实例Promise。记住,无论是
普通方法还是原型方法都返回一个值,Promise这能让你更容易
理解它们。

原型方法

我们先从prototype methods这三个方法开始。再次
强调一下,所有这些方法都可以应用于
Promise对象实例,并且都会返回一个 Promise。
以下所有方法都会为 Promise 的不同状态转换分配处理程序。
正如我们之前看到的,当 PromisePromise创建时,它处于pending初始状态。当 Promise 被结算时,以下三个方法中的一个或多个将根据 其状态(已结算或未结算)
运行
fulfilledrejected

Promise.prototype.catch(onRejected)

Promise.prototype.then(onFulfilled, onRejected)

Promise.prototype.finally(onFinally)

.then下图展示了 `finally`和 ` reject` 方法的流程.catch。由于它们
返回一个 ` PromisePromise`,因此可以链式调用,图中也显示了这一点。
如果`finally` 是为 Promise 声明的,那么无论 Promise 是已完成还是已拒绝,只要有 Promise.finally状态,它就会执行。正如Konstantin Rouda指出的,`finally` 的支持有限 ,因此请在使用前进行检查。
settled

来源:
https://mdn.mozillademos.org/files/15911/promises.png

这是一个小故事。你是一个学生,你问妈妈要一部
手机。她说:“我月底会给你买一部手机。”

让我们来看看如果 Promise 在
月底执行,它在 JavaScript 中会是什么样子。

var momsPromise = new Promise(function(resolve, reject) {
  momsSavings = 20000;
  priceOfPhone = 60000;
  if (momsSavings > priceOfPhone) {
    resolve({
      brand: "iphone",
      model: "6s"
    });
  } else {
    reject("We donot have enough savings. Let us save some more money.");
  }
});

momsPromise.then(function(value) {
  console.log("Hurray I got this phone as a gift ", JSON.stringify(value));
});

momsPromise.catch(function(reason) {
  console.log("Mom coudn't buy me the phone because ", reason);
});

momsPromise.finally(function() {
  console.log(
    "Irrespecitve of whether my mom can buy me a phone or not, I still love her"
  );
});

输出结果为:


妈妈食言了。

如果我们把这个值改成momsSavings200000,那么妈妈就可以给
儿子送礼物了。在这种情况下,输出结果将是:


妈妈信守承诺。

让我们设身处地地站在这个库的使用者的角度来思考。我们模拟它的
输出和特性,以便观察如何有效地使用和捕捉它们。

由于.then可以同时赋值两者onFulfilled, onRejected handlers,与其
分别编写.then.catch我们可以对两者进行相同的操作
.then。它看起来会像下面这样。

momsPromise.then(
  function(value) {
    console.log("Hurray I got this phone as a gift ", JSON.stringify(value));
  },
  function(reason) {
    console.log("Mom coudn't buy me the phone because ", reason);
  }
);

但为了提高代码的可读性,我认为最好将它们分开。

为了确保所有示例都能在浏览器(尤其是 Chrome)中运行,
我保证代码示例中没有任何外部依赖项
。为了更好地理解后续内容,我们创建一个函数
,该函数返回一个 Promise 对象,该对象会被随机解析或拒绝,以便
我们测试各种场景。为了理解异步
函数的概念,我们还会在函数中引入一个随机延迟。由于我们
需要随机数,因此我们首先创建一个随机函数,该函数会返回一个
介于 x 和 y 之间的随机数。

function getRandomNumber(start = 1, end = 10) {
  //works when both start,end are >=1 and end > start
  return parseInt(Math.random() * end) % (end-start+1) + start;
}

让我们创建一个函数,它会返回一个 Promise。我们调用这个
函数promiseTRRARNOSG,它是另一个函数的别名
promiseThatResolvesRandomlyAfterRandomNumnberOfSecondsGenerator。这个函数
会创建一个 Promise,它会在 2 到 10 秒之间的随机时间后被解析或拒绝。
为了使解析和拒绝随机化,我们会生成一个
1 到 10 之间的随机数。如果生成的随机数大于 5,则
解析 Promise,否则拒绝它。

function getRandomNumber(start = 1, end = 10) {
  //works when both start and end are >=1
  return (parseInt(Math.random() * end) % (end - start + 1)) + start;
}

var promiseTRRARNOSG = (
 = function() {
  return new Promise(function(resolve, reject) {
    let randomNumberOfSeconds = getRandomNumber(2, 10);
    setTimeout(function() {
      let randomiseResolving = getRandomNumber(1, 10);
      if (randomiseResolving > 5) {
        resolve({
          randomNumberOfSeconds: randomNumberOfSeconds,
          randomiseResolving: randomiseResolving
        });
      } else {
        reject({
          randomNumberOfSeconds: randomNumberOfSeconds,
          randomiseResolving: randomiseResolving
        });
      }
    }, randomNumberOfSeconds * 1000);
  });
});

var testProimse = promiseTRRARNOSG();
testProimse.then(function(value) {
  console.log("Value when promise is resolved : ", value);
});
testProimse.catch(function(reason) {
  console.log("Reason when promise is rejected : ", reason);
});

// Let us loop through and create ten different promises using the function to see some variation. Some will be resolved and some will be rejected. 

for (i=1; i<=10; i++) {
  let promise = promiseTRRARNOSG();
  promise.then(function(value) {
    console.log("Value when promise is resolved : ", value);
  });
  promise.catch(function(reason) {
    console.log("Reason when promise is rejected : ", reason);
  });
}

刷新浏览器页面并在控制台中运行代码,即可查看不同场景下的
输出结果。接下来,我们将学习如何 创建多个 Promise 并检查它们的输出,而无需执行此操作。resolvereject

静态方法

对象中有四个静态方法Promise

前两个是辅助方法或快捷方式,它们可以帮助你
轻松创建已解决或已拒绝的 Promise。

Promise.reject(reason)

帮助你创建一个被拒绝的承诺。

var promise3 = Promise.reject("Not interested");
promise3.then(function(value){
  console.log("This will not run as it is a resolved promise. The resolved value is ", value);
});
promise3.catch(function(reason){
  console.log("This run as it is a rejected promise. The reason is ", reason);
});

Promise.resolve(value)

帮助你做出坚定的承诺。

var promise4 = Promise.resolve(1);
promise4.then(function(value){
  console.log("This will run as it is a resovled promise. The resolved value is ", value);
});
promise4.catch(function(reason){
  console.log("This will not run as it is a resolved promise", reason);
});

另外,一个 Promise 可以有多个处理程序。因此,您可以将上面的
代码更新为:

var promise4 = Promise.resolve(1);
promise4.then(function(value){
  console.log("This will run as it is a resovled promise. The resolved value is ", value);
});
promise4.then(function(value){
  console.log("This will also run as multiple handlers can be added. Printing twice the resolved value which is ", value * 2);
});
promise4.catch(function(reason){
  console.log("This will not run as it is a resolved promise", reason);
});

输出结果如下所示。

接下来的两种方法可以帮助你处理一组 Promise。当需要处理
多个 Promise 时,最好先创建一个 Promise 数组,然后
再对这个数组执行必要的操作。为了更好地理解这些
方法,我们不能直接使用随机生成的Promise promiseTRRARNOSG,因为它过于
随机。最好使用一些确定性的 Promise,这样我们才能
更好地理解其行为。让我们创建两个函数:一个在 n 秒后解析成功
,另一个在 n 秒后拒绝成功。

var promiseTRSANSG = (promiseThatResolvesAfterNSecondsGenerator = function(
  n = 0
) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve({
        resolvedAfterNSeconds: n
      });
    }, n * 1000);
  });
});
var promiseTRJANSG = (promiseThatRejectsAfterNSecondsGenerator = function(
  n = 0
) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      reject({
        rejectedAfterNSeconds: n
      });
    }, n * 1000);
  });
});

现在让我们使用这些辅助函数来理解Promise.All

承诺。所有

根据 MDN 文档

Promise.all(iterable)方法返回一个单一的PromisePromise,当参数
中的所有 Promiseiterable都已解决或
可迭代参数中不包含任何 Promise 时,该 Promise 将被解析。如果参数被拒绝,则该方法返回第一个被拒绝的 Promise 的拒绝原因

情况一:所有承诺都已解决。这是最常用
的场景。

console.time("Promise.All");
var promisesArray = [];
promisesArray.push(promiseTRSANSG(1));
promisesArray.push(promiseTRSANSG(4));
promisesArray.push(promiseTRSANSG(2));
var handleAllPromises = Promise.all(promisesArray);
handleAllPromises.then(function(values) {
  console.timeEnd("Promise.All");
  console.log("All the promises are resolved", values);
});
handleAllPromises.catch(function(reason) {
  console.log("One of the promises failed with the following reason", reason);
});

所有承诺均已兑现。

从输出结果中,我们需要得出两个重要的观察结论。

首先:第三个 Promise 耗时 2 秒,比
耗时 4 秒的第二个 Promise 更早完成。但正如你在输出中看到的,
Promise 的顺序在值中得到了保持。

第二点:我添加了一个控制台计时器来测量执行时间Promise.All
如果这些 Promise 是顺序执行的,那么
总共应该需要 1+4+2=7 秒。但从计时器来看,实际只用了 4 秒。这证明
所有 Promise 都是并行执行的。

**情况二:**当没有任何承诺时。我认为这种情况最少
使用。

console.time("Promise.All");
var promisesArray = [];
promisesArray.push(1);
promisesArray.push(4);
promisesArray.push(2);
var handleAllPromises = Promise.all(promisesArray);
handleAllPromises.then(function(values) {
  console.timeEnd("Promise.All");
  console.log("All the promises are resolved", values);
});
handleAllPromises.catch(function(reason) {
  console.log("One of the promises failed with the following reason", reason);
});

由于数组中没有任何 Promise,因此返回的 Promise 会被解析。

**情况 3:**它以第一个被拒绝的承诺所为由拒绝。

console.time("Promise.All");
var promisesArray = [];
promisesArray.push(promiseTRSANSG(1));
promisesArray.push(promiseTRSANSG(5));
promisesArray.push(promiseTRSANSG(3));
promisesArray.push(promiseTRSANSG(4));
var handleAllPromises = Promise.all(promisesArray);
handleAllPromises.then(function(values) {
  console.timeEnd("Promise.All");
  console.log("All the promises are resolved", values);
});
handleAllPromises.catch(function(reason) {
  console.timeEnd("Promise.All");
  console.log("One of the promises failed with the following reason ", reason);
});

第一次拒绝后,执行停止。

承诺.竞赛

根据MDN文档

Promise.race(iterable)方法返回一个 Promise,该Promise 会
在可迭代对象中的一个 Promise 解决或拒绝时立即解决或拒绝,并返回
该 Promise 的值或原因。

情况 1:其中一个承诺先得到解决。

console.time("Promise.race");
var promisesArray = [];
promisesArray.push(promiseTRSANSG(4));
promisesArray.push(promiseTRSANSG(3));
promisesArray.push(promiseTRJANSG(3));
promisesArray.push(promiseTRSANSG(4));
var promisesRace = Promise.race(promisesArray);
promisesRace.then(function(values) {
  console.timeEnd("Promise.race");
  console.log("The fasted promise resolved", values);
});
promisesRace.catch(function(reason) {
  console.timeEnd("Promise.race");
  console.log("The fastest promise rejected with the following reason ", reason);
});


最快分辨率

所有 Promise 都是并行执行的。第三个 Promise 会在 2 秒后解析。
一旦第三个 Promise 解析完成,返回的 PromisePromise.race也会立即被解析。

情况二:其中一位承诺者首先拒绝了对方。

console.time("Promise.race");
var promisesArray = [];
promisesArray.push(promiseTRSANSG(4));
promisesArray.push(promiseTRSANSG(6));
promisesArray.push(promiseTRSANSG(5));
promisesArray.push(promiseTRSANSG(4));
var promisesRace = Promise.race(promisesArray);
promisesRace.then(function(values) {
  console.timeEnd("Promise.race");
  console.log("The fasted promise resolved", values);
});
promisesRace.catch(function(reason) {
  console.timeEnd("Promise.race");
  console.log("The fastest promise rejected with the following reason ", reason);
});

最快拒绝

所有 Promise 并行执行。第四个 Promise 在 3 秒后被拒绝。
一旦这一步完成,返回的 PromisePromise.race也会被拒绝。

我编写了所有示例方法,以便测试各种场景
,并且测试可以直接在浏览器中运行。这就是为什么示例中没有
API 调用、文件操作或数据库调用的原因。虽然这些都是
实际应用场景,但你需要额外花费精力来设置和测试它们。
而使用延迟函数则可以让你轻松实现类似的场景,无需
额外的设置。你可以轻松地调整这些值来查看和
测试不同的场景。你可以结合使用 `and`promiseTRJANSG` methods` 来模拟足够多的场景,从而 深入理解 Promise。此外,在相关代码块前后使用`methods` 可以帮助我们轻松识别 Promise 是并行执行 还是顺序执行。如果你有其他有趣的场景,或者我遗漏了什么,请告诉我 。如果你想在一个地方查看所有代码示例, 请查看这个 gist。
promiseTRSANSGpromiseTRRARNOSG
console.time



Bluebird 有一些有趣的功能,例如

  1. Promise.prototype.timeout
  2. 承诺。一些
  3. 承诺。

我们将在另一篇文章中讨论这些问题。

我还会再写一篇关于我从 async 和 await 中学到的东西的文章。

在结束之前,我想列出我为了
在承诺方面保持理智而遵循的所有经验法则。

使用 Promise 的经验法则

  1. 在编写异步或阻塞代码时,请使用 Promise。
  2. resolve在所有实际应用中,映射thenreject映射。catch
  3. 请确保为所有 Promise编写 `and`.catch和 ` methods` 方法。.then
  4. 如果两种情况下都需要做某事,请使用.finally
  5. 我们只有一次机会来改变每一个承诺。
  6. 我们可以向同一个 Promise 添加多个处理程序。
  7. 对象中所有方法的返回类型,Promise无论是静态方法还是原型方法,都是一个Promise
  8. 承诺的顺序Promise.all是保持不变的,无论哪个承诺最先得到解决。
文章来源:https://dev.to/nkgokul/understanding-promises-in-javascript-239a