通过赌场赌博来解释 JavaScript Promise

2025-06-09

通过赌场赌博来解释 JavaScript Promise

如果您曾经赌博或看过有关赌博的电影,那么您就可以理解 JavaScript 中的承诺。

我们都喜欢 JavaScript 的异步功能。事实上,我们爱得太深,以至于有时甚至会过度沉迷。然后我们得到的就是看起来像“毁灭金字塔”的代码。

图片来源

这通常被称为“回调地狱”,因为你很可能不想重读代码,试图理解所有代码的工作原理和执行顺序。事实上,你的团队里也没有人这么做。

上述示例有几个难点:

  • 错误处理不明确。如果出错了怎么办?
  • 每个函数都依赖于前一个函数。你不需要异步风格。你希望其他人阅读代码时能够清楚地了解其顺序。当你将这么多函数串联在一起时,同步风格的代码会更具可读性。
  • 你需要持续追踪函数的输入变量,以及输出变量。同时还要追踪每个输出的逻辑。这会让人精疲力尽。

你可以使用Promise让整个过程更容易理解。如果你和我一样,可能听说过 Promise 一两次,但后来就忽略了它们,因为它们看起来令人困惑。如果你理解回调,Promise 的基本用法其实相当简单。

Promise 鼓励使用简单、单一用途的函数,这样你就能编写清晰的代码,并且轻松理解每个步骤。思考了一会儿之后,我意识到 Promise 就像去赌场一样。赌场会“修改”你银行账户里的钱(嗯,是取出),而一系列 Promise 则会按照特定的顺序修改数据。

那么,让我们开始吧。如果你没有使用过回调,可以看看我关于回调原理的讲解。如果你想了解关于 Promise 的更技术性的解释,可以看看这个指南这个指南或者这个视频

什么是承诺?

假设你周末去赌场度假。你口袋里有两周的工资,打算尽情享受赌博的乐趣,把钱输得精光。又或许,你会走运,最终赢钱?

你回到酒店房间,然后前往赌场。每种游戏都接受现金,所以你需要去ATM机取1000美元开始游戏。

让我们退一步思考一下这个场景。虽然现金在赌场外可以用于任何用途,但在赌场内,它只意味着一件事——在你耗尽资金之前,你还能玩多少场游戏。这笔现金可能会在周末越来越少。它也可能会增加,但你已经承诺过自己,这个周末不会输超过1000美元。

注意上图中你的剩余金额是如何从一个游戏转移到另一个游戏的吗?

Promise 代表着一个目前尚不存在但未来必定会存在的值。这能让你清晰地追踪函数,并理解其起始和结束。如上所示,Promise 是清晰地定义连续异步函数并明确输入和输出的绝佳方式。

Promise 将一个异步函数的结果直接传递给下一个函数。该函数将在前一个函数返回值后立即启动。或者,如果它返回错误,则将运行另一个函数。我们稍后可以讨论这种应对措施。

创建你的第一个承诺

实际上有两种类型的承诺:生产者消费者

生产者是链中的第一个 Promise,而消费者则等待链中前一个 Promise 的结果。在本例中,去 ATM 机取钱就是生产者,因为玩游戏需要钱(这很显然)。

此外,承诺可以具有以下三种状态之一:

  1. 待定-尚未完成
  2. Fulfilled- Promise 已完成并返回值
  3. 被拒绝 — 承诺已完成但出现错误或失败。

所以,如果你去ATM机取款,却没能完成预期的操作……那么,你的银行账户里可能没有1000美元,你应该立即离开赌场。如果你成功取出1000美元,那么你就得到了返还。

那么让我们把它变成代码吧。这是 Promise 的语法。

let withdraw = new Promise(function(resolve,reject){

  let amount = visitATM(1000);
  return resolve(amount)
});
Enter fullscreen mode Exit fullscreen mode

以下是该代码的逐行解释。

promise代码块2.jpg

第 1 行 - 声明承诺,有两种可能的结果:履行或拒绝

第 2 行-从 ATM 取款的功能

第 3 行 - 使用 visitATM 函数的值返回稳定状态

与其他异步代码一样,此方法允许您的代码等待 visitATM 函数的状态。如果该函数未完成,则继续执行毫无意义!

链接多个 Promise

假设您想在赌场玩老虎机、扑克和轮盘赌。每种游戏都需要您用现金买入。当然,如果您在扑克上投注过多资金,导致资金耗尽,那么您将无法玩以下任何游戏。

假设您首先想玩老虎机。

let withdraw = new Promise(function(resolve,reject){ 

  let amount = visitATM(1000); 

  return resolve(amount) 
}); 

withdraw.then(function(amount){
  let slotResults = playSlots(amount, 100);

  if(slotResults <= 0)
    throw err;

  return slotResults;
});
Enter fullscreen mode Exit fullscreen mode

Promise 使用.then语法来表示在前一个 Promise完成(或结算)后应该发生什么。在本例中,withdraw Promise 的最终结果包含在amount中。

因此,当我们使用 .then() 设置下一个承诺时,我们也会将参数数量命名为与之前的结果相对应。

另一个需要注意的是,playSlots 是一个虚构的函数。我们假设它需要两个参数:你拥有的总金额以及你愿意下注的金额。

让我们在这个承诺链上再添加一步——玩一场扑克游戏。它的工作原理与老虎机承诺类似。在这个游戏中,我们可以尽情下注。

withdraw.then(function(amount){
  let slotResults = playSlots(amount, 100);

  if(slotResults <= 0)
    throw err;

  return slotResults;
})
.then(function(slotResults){
  let pokerResults = playPoker(slotResults);

  if(pokerResults <= 0) 
    throw err; 

  return pokerResults;
})
Enter fullscreen mode Exit fullscreen mode

所以,我们把玩完老虎机剩下的钱都用来玩扑克了。要我说,这还真是够狠的。

这是该部分的代码图。

假设我们现在已经把所有的钱都赌光了。虽然我们原本打算继续玩,但现在已经没有钱了。这条链条上可能会有更多承诺,但我们却无力兑现。

相反,由于扑克牌局结束后我们剩余的金额为 0 美元,这个 Promise 会抛出错误。它仍然处于“已解决”状态,但处于“拒绝”状态。

这时.catch() 方法就派上用场了。Catch 方法允许我们处理 Promise 链中可能发生的任何错误。我们无需为每个回调编写错误处理程序。

假设你输光了所有钱后直接去了酒吧。代码如下:

withdraw.then(function(amount){
  let slotResults = playSlots(amount, 100);

  if(slotResults <= 0)
    throw err;

  return slotResults;
})
.then(function(slotResults){
  let pokerResults = playPoker(slotResults);

  if(pokerResults <= 0) 
    throw err; 

  return pokerResults;
})
.catch(function(e){
  goToBar();
});
Enter fullscreen mode Exit fullscreen mode

无论哪个承诺被拒绝,这个 catch 语句都会起作用。

在 Promise 中使用对象

到目前为止,我们的承诺只返回了一个数字。但是,它们也可以在链中传递任何其他类型的数据。

假设你玩了老虎机,赢了一些钱。老虎机不会直接给你现金,而是给你一张票,你可以稍后兑换。这叫做“票进票出”系统

现在,你需要在整个链条中追踪两个值——库存现金和票面价值。在这种情况下,使用对象是最合适的。

让我们修改链中的第二个承诺,即您玩老虎机的地方。

withdraw.then(function(amount){
  let ticketValue = playSlots(amount, 100);

  if(ticketValue <= 0)
    throw err;

  return {tickets: ticketValue, cash: amount};
});
Enter fullscreen mode Exit fullscreen mode

现在返回一个具有两个属性的对象。如下所示:

扑克牌桌只接受用现金购买筹码,因此您需要在下一个承诺中使用该属性。

withdraw.then(function(amount){
  let ticketValue = playSlots(amount, 100);

  if(ticketValue <= 0)
    throw err;

  return {tickets: ticketValue, cash: amount};
})
.then(function(slotResults){
  let pokerResults = playPoker(slotResults.cash);

  if(pokerResults <= 0) 
    throw err; 

  return {tickets: slotResults.tickets, cash: pokerResults};
})
.catch(function(e){
  goToBar();
});
Enter fullscreen mode Exit fullscreen mode

请注意以下几点:

  1. 我在扑克游戏中只使用了现金价值。但最后,我仍然需要将票面价值添加到最终对象中,以便将其传递到链中。否则,我就会失去我的奖金。
  2. slotResults 包含来自前一个承诺的对象,即使该对象没有名称。

获取最新教程

你喜欢这个讲解吗?查看CodeAnalogies 博客,获取最新的 HTML、CSS 和 JavaScript 可视化教程。

鏂囩珷鏉ユ簮锛�https://dev.to/kbk0125/javascript-promises-explained-by-gambling-at-a-casino-4jdo
PREV
通过节食解释 JavaScript 的 Reduce 方法
NEXT
通过经营啤酒厂来解释亚马逊网络服务 (AWS)