当你使用 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 完全类型化版本,以使代码更有用。我们将在以后的文章中回到承诺;还有更多要说的!
本文暂时没有评论,来添加一个吧(●'◡'●)