专业的编程技术博客社区

网站首页 > 博客文章 正文

Promises 的隐藏状态(implosion隐藏区域)

baijin 2024-08-19 11:31:15 博客文章 12 ℃ 0 评论


当你使用 promise 时,你知道它们可能处于不同的状态,有时你可能需要了解给定 promise 的具体状态——但如何获取它呢?JavaScript 没有为这个问题提供现成可用的解决方案!

因此,在本文中,我们将找到一种方法,通过仅使用可用的标准属性和方法来确定承诺的当前状态,这样我们的代码就可以确保在任何地方都能正常工作。

承诺的可能状态是什么?

Promise 可以处于三种状态:

  • Pending,当它们尚未解析为值或因错误而被拒绝时
  • Fulfilled,当他们解决了一个值
  • Rejected, 当他们拒绝了

待定的承诺被称为“ unsettled”,而已履行和拒绝的承诺被称为“已解决”。

我们可以很容易地看到所有三种状态,只需很少的编码:

const a = new Promise((resolve,reject) => { /* nothing */ });
// Promise {<pending>}
const b = Promise.resolve(22);
// Promise {<fulfilled>: 22}
const c = Promise.reject("bad");
// Promise {<rejected>: 'bad'}

很明显,promises 在内部以某种方式跟踪它们的状态,但是没有我们可以访问它的可见的公共属性。如果我们尝试Object.keys(somePromise),我们会得到一个空数组。

所以,如果我们想获得承诺的状态,我们需要找到一些方法来使用可用的函数;没有我们可以使用的“后门”。幸运的是,有一种方法可以实现这一点,我们将在接下来看到。

实施我们的方法

以我们之前的Waiting For Some Promises为例?在本文中,我们将定义一个独立的函数并修改Promise.prototype以简化获得所需状态的过程。

Promise.prototype.status = function () {
  return promiseStatus(this);
};

这里的问题是我们无法同步获取状态;我们必须为此编写一个async函数。如何?我们可以Promise.race()为此使用。

当您调用Promise.race()一系列承诺时,它会在任何承诺得到解决后立即解决。考虑到这一点,我们可以称之为提供两个承诺:一个是我们关心的,另一个是已经确定的。这将如何工作?

  • 如果Promise.race()解析为我们已经确定的承诺返回的值,则另一个未决。
  • 如果Promise.race()解析为其他任何内容,则它要么具有值(因此,承诺已实现),要么由于某种原因导致错误(承诺被拒绝。)

所以,我们可以这样写:

const promiseStatus = async (p) => {
  const SPECIAL = Symbol("status");

  return Promise.race([p, Promise.resolve(SPECIAL)]).then(
    (value) => (value === SPECIAL ? "pending" : "fulfilled"),
    (reason) => "rejected"
  );
};

(给眼尖的 JavaScript 开发者的提示:我们真的不需要async在第一行指定;根据定义,返回 promise 的函数已经是async.)

这是如何运作的?我们必须确保我们已经确定的承诺解析为其他代码无法解析的值。使用 asymbol是关键;符号保证是唯一的。

因此,如果Promise.race()解析为某个值,如果它是我们的符号,则意味着我们要测试的承诺仍然悬而未决。否则,如果比赛解决为任何其他价值,则其他承诺得到履行。另一方面,如果比赛以拒绝结束,我们可以肯定地知道被测试的承诺被拒绝了。(您可能想查看.then()工作原理。)

我们做到了!我们的promiseStatus()方法是一个async,但是马上就解决了。让我们看看如何测试它!

测试我们的方法

首先,我们需要一些虚拟的承诺,我们可以重用我们在等待一些承诺?文章。

const success = (time, value) =>
  new Promise((resolve) => setTimeout(resolve, time, value));

const failure = (time, reason) =>
  new Promise((_, reject) => setTimeout(reject, time, reason));

success()函数返回一个承诺,该承诺将在一段时间后解析为一个值,该failure()函数返回一个承诺,该承诺将在一段时间后以某种原因拒绝。现在我们可以编写以下测试,如果我们愿意,可以将其转换为Jest单元测试。

const ppp = success(200, 33);
const qqq = failure(500, "Why not?");

promiseStatus(ppp).then(console.log);   // pending
promiseStatus(qqq).then(console.log);   // pending

ppp.then(() => {  
  promiseStatus(ppp).then(console.log); // fulfilled
  promiseStatus(qqq).then(console.log); // pending
});

qqq.catch(() => {
  promiseStatus(ppp).then(console.log); // fulfilled
  promiseStatus(qqq).then(console.log); // rejected
});

让我们分析一下结果。前两个测试产生“待定”结果,因为还没有时间来解决任何承诺。如果我们等到ppp解决了,它的状态就会变成“已完成”,但qqq仍然是“待定”。如果我们然后等到qqqsettled,ppp仍然是“fulfilled”(那里没有变化)但现在qqq是“rejected”——完全正确!

输入我们的方法

我们必须从我们的promiseStatus()功能开始。PromiseStatus我们可以用三种可能的结果定义一个辅助类型。我们的promiseStatus()函数将 aPromise<any>作为参数并返回Promise<PromiseStatus>结果。

type PromiseStatus = "pending" | "fulfilled" | "rejected";

const promiseStatus = (p: Promise<any>): Promise<PromiseStatus> => {
  const SPECIAL = Symbol("status");

  return Promise.race([p, Promise.resolve(SPECIAL)]).then(
    (value) => (value === SPECIAL ? "pending" : "fulfilled"),
    (reason) => "rejected"
  );
};

要修改Promise.prototype,我们必须添加一个全局定义,如下所示。

declare global {
  interface Promise<T> {
    status(): Promise<PromiseStatus>;
  }
}

有了这个定义,我们现在可以将我们的函数添加到Promise.prototype.

Promise.prototype.status = function () {
  return promiseStatus(this);
};

这样,我们就可以直接使用 status 方法,如下所示:

ppp.status().then(console.log); 

或者

const rrr = await qqq.status();
if (rrr === ...) { ... }

我们完成了!

结论

在本文中,我们解决了了解承诺状态的问题,并找到了一种迂回的方式来获取它,因为限制不允许我们直接访问它。我们还制作了一个 TypeScript 完全类型化版本,以使代码更有用。我们将在以后的文章中回到承诺;还有更多要说的!

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表