如何检查一个对象是否是 Promise?

504
无论是 ES6 的 Promise,还是 Bluebird 的 Promise、Q 的 Promise 等等。
我该如何测试一个给定的对象是否为 Promise

4
你最多只能检查一个对象是否有 .then 方法,但这并不能确定你得到的是一个 Promise。此时你所知道的只是该对象具有类似 Promise 的 .then 方法。 - Scott Offen
@ScottOffen 承诺规范_明确地_没有区别。 - Benjamin Gruenbaum
8
我的观点是任何人都可以创建一个暴露了.then方法的对象,但这并不意味着它是Promise,也不会像Promise一样运作,并且可能根本没想过要像Promise一样使用它。检查是否存在.then方法只能告诉你如果对象没有.then方法,则你没有Promise。相反,存在.then方法并不一定意味着你Promise。 - Scott Offen
5
按照定义,确认一个Promise对象的唯一方法是检查它是否有.then方法。是的,这种方法可能会出现误判,但所有的Promise库都依赖于这种假设(因为它们只能依赖于这个)。我所能想到的唯一替代方案是采用Benjamin Gruenbaum提出的建议并将其运行通过Promise测试套件。但对于实际的生产代码来说,这不太实际。 - JLRishe
3
const isPromise = v => typeof v === 'object' && typeof v.then === 'function' 可以翻译为“判断变量是否为 Promise 的函数,判断依据是变量的类型为对象并且具有 then 方法”。 - Dominic
显示剩余3条评论
20个回答

487

一个Promise库如何决定

如果它有一个.then函数 - 这是标准的Promise库使用的 唯一 方法。

Promise/A+规范中有一个叫做thenable的概念,它基本上是“具有then方法的对象”。Promise将并且应该同化任何具有then方法的东西。你提到的所有Promise实现都这样做。

如果我们查看规范:

2.3.3.3 如果then是一个函数,则使用x作为this,第一个参数resolvePromise,第二个参数rejectPromise来调用它

它还解释了这个设计决策的原因:

这种对于thenable的处理方式允许Promise实现进行互操作,只要它们公开符合Promises/A+的then方法即可。它还允许Promises/A+实现通过合理的then方法“同化”不符合规定的实现。

你应该如何决定

你不应该 - 相反,调用Promise.resolve(x)(在Q中调用Q(x)),它将 始终 将任何值或外部thenable转换为可信赖的Promise。这比自己执行这些检查更安全和更容易。

真的需要确定吗?

你总是可以通过测试套件来验证 :D


4
执行 Promise.resolve(x) (Q(x) 在 Q 中) 如何告诉您 x 是否为一个 Promise? - Ben
3
@Ben,这并不重要,你几乎永远不需要关心某个东西是否是一个Promise - Promise.resolve 会自动为您处理这个问题 - 您始终会获得一个Promise。 - Benjamin Gruenbaum
@BenjaminGruenbaum 但是为什么 Promise.resolve(a_promise) 会返回 a_promise 呢? - Onkeltem
2
此外,在async上下文中,await thing会使其解析为thing的值或其承诺值。 - Onkeltem
@JoeFlack可能需要详细阐述为什么/如何它不能帮助React案例,或者发布并链接到单独的问题。 - mtraceur
@mtraceur 很抱歉,我不小心删掉了我的评论。我说这对React没有帮助。我很懒,没有尝试他的回答,现在我想尝试一下。更重要的是,这个答案并没有回答OP的问题。它没有考虑到个人使用/边缘情况。至少应该说明你不能检查,或者说这很困难。而不是只是把它写掉,然后继续谈论设计理念。否则,这个答案还可以。 - Joe Flack

310

检查某些内容是否为 Promise 会使代码变得不必要的复杂,只需使用 Promise.resolve

Promise.resolve(valueOrPromiseItDoesntMatter).then(function(value) {

})

3
@AlexMills 是的,它甚至可以用于非标准的 Promise,如 jQuery Promise。如果对象具有一个 then 方法,但其接口与 Promise 的 then 完全不同,则可能会失败。 - Esailija
79
这个回答虽然可能是好建议,但实际上并没有回答问题。 - Stijn de Witt
6
除非问题确实是关于有人实际实现一个Promise库,否则这个问题是无效的。只有Promise库需要进行检查,之后你可以像我展示的那样始终使用其.resolve方法。 - Esailija
11
这个问题对于承诺库的实施者来说似乎是相关和重要的,但对于一个承诺库的用户来说也很重要。他想知道实现会/应该/可能如何行事以及不同的承诺库将如何相互作用。特别是,这位用户非常沮丧的是,明显的事实是,我可以为除“promise”之外的任何X做一个X的承诺(无论这里的“promise”意味着什么 - 这就是问题所在),而我肯定有兴趣确切地了解这个例外规则的界限在哪里。 - Don Hatch
3
@Esailija 我有一个变量,用于指示代码是否已加载。这个变量可能是一个承诺(promise),表示正在加载,我需要等待;或者是一个值,表示已经加载完毕,因此我可以立即渲染它。值得一提的是,如果仍在加载中,我将呈现一个非常复杂的加载动画。因此,我不能只是一直等待,因为如果我总是等待,即使代码已经准备好了,render()函数每次都会被调用,并创建整个加载动画。 - SCLeo
显示剩余16条评论

120

免责声明:这不是对更新的OP的好答案,是针对每个库的,并且在不同领域中无法工作。请使用.then进行检查。

这个答案,基于规范,是一种只有在某些时候才有效的测试Promise的方法,供参考。

Promise.resolve(obj) == obj &&
BLUEBIRD.resolve(obj) == obj

当这个工作时,是因为算法明确要求Promise.resolve必须返回传入的确切对象,仅当它是由此构造函数创建的promise

18
在使用JavaScript时,你是否应该使用===代替==呢? - Neil S
1
@NeilS == is fine. - jib
16
对于不属于相同领域的 Promise,此方法也会失败。 - Benjamin Gruenbaum
5
“按规范的定义而言所做出的承诺”,似乎意味着“通过 Promise.resolve() 创建的 Promise 与同一构造函数创建的承诺相同”- 因此,这将无法检测到例如使用 polyfill 创建的 Promise 是否真正是 Promise。 - VoxPelli
5
如果您可以先说明您是如何解释这个问题的,而不是立即给出答案,那么这个答案可能会更好--不幸的是,提问者并没有说明清楚,您也没有说明清楚,所以此时提问者、作者和读者可能都在三个不同的页面上。您所提到的文件指出:"如果参数是由此构造函数生成的Promise",斜体部分至关重要。说明您回答的是这个问题。同时指出,您的答案对于这个库的使用者很有用,但不适用于实现者。 - Don Hatch
显示剩余4条评论

115

免责声明:这不是对更新后的OP的好答案,仅适用于本地使用,不能跨越领域。请按照被接受的答案进行操作。

obj instanceof Promise

应该这样做。请注意,这可能仅在使用本地es6承诺时可靠工作。

如果您正在使用模拟器、承诺库或任何其他类似于承诺的东西,则更适合测试一个“thenable”(具有.then方法的任何内容),如此处的其他答案所示。


4
这个不可靠,导致我遇到了一个难以追踪的问题。比如你有一个使用es6.promise shim的库,然后你在某个地方使用了Bluebird,就会出现问题。这个问题在Chrome Canary中出现过。 - vaughan
2
是的,这个答案实际上是错误的。我也因为一个难以追踪的问题而来到这里。你真的应该检查 obj && typeof obj.then == 'function',因为它适用于所有类型的 promises,并且实际上是规范推荐并被实现和填充物使用的方式。例如,原生的 Promise.all 将适用于所有的 thenables,不仅仅是其他原生 promises。所以你的代码也应该如此。因此,instanceof Promise 不是一个好的解决方案。 - Stijn de Witt
2
问题出在问题本身,而不是答案。正确的问题应该是“如何判断一个对象是否可thenable?使用鸭子类型,我们本质上不需要关心对象的类型是什么。” - jib
2
跟进 - 更糟糕的是:在使用原生 Promise 的 Node.js 6.2.2 上,我现在正在尝试调试一个问题,即 console.log(typeof p, p, p instanceof Promise); 产生了这样的输出:object Promise { <pending> } false。正如您所看到的,它确实是一个 Promise - 但是 instanceof Promise 测试返回了 false - Mörre
2
这将对不属于同一领域的承诺失败。 - Benjamin Gruenbaum
显示剩余3条评论

73
if (typeof thing?.then === 'function') {
    // probably a promise
} else {
    // definitely not a promise
}

9
如果某个东西未定义,你需要通过使用thing && ...来防范它。 - mrBorna
2
不是最好的,但肯定很有可能;这也取决于问题的范围。在开放式公共API或您知道数据的形状/签名完全是开放式的情况下,编写100%的防御性通常是适用的。 - rob2d
5
@mrBorna 在 thing?.then 中的 ? 处理了对未定义变量的检查。这被称为“可选链”。阅读更多信息:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Optional_chaining - Ajay Gupta
if (p && 'then' in p && typeof p.then === 'function') - Jonathan
3
答案实际上回答了问题,而不是转移注意力到其他指导。谢谢! - derpedy-doo

37

要判断给定的对象是否为ES6 Promise,我们可以使用以下谓词:

function isPromise(p) {
  return p && Object.prototype.toString.call(p) === "[object Promise]";
}

Call直接从Object.prototype调用toString会返回给定对象类型的本机字符串表示,在我们的例子中是"[object Promise]"。这确保了给定对象

  • 绕过误报,例如..:
    • 具有相同构造函数名称("Promise")的自定义对象类型。
    • 给定对象的自编写toString方法。
  • 可以跨多个环境上下文工作(例如iframes)instanceofisPrototypeOf相比
然而,任何一个主机对象,如果它通过 Symbol.toStringTag 进行 标记修改,都可以返回"[object Promise]"。这可能是预期的结果,也可能取决于项目情况(例如,如果存在自定义 Promise 实现)。
为了判断对象是否来自于一个ES6原生的Promise,我们可以使用:
function isNativePromise(p) {
  return p && typeof p.constructor === "function"
    && Function.prototype.toString.call(p.constructor).replace(/\(.*\)/, "()")
    === Function.prototype.toString.call(/*native object*/Function)
      .replace("Function", "Promise") // replacing Identifier
      .replace(/\(.*\)/, "()"); // removing possible FormalParameterList 
}

根据规范的this此部分,函数的字符串表示应为:

"function Identifier ( FormalParameterListopt ) { FunctionBody }"

上述内容已得到相应处理。在所有主流浏览器中,FunctionBody均为[native code]

MDN:Function.prototype.toString

这也适用于多个环境上下文。


26

这是 graphql-js 包检测 promises 的方法:

function isPromise(value) {
  return Boolean(value && typeof value.then === 'function');
}

value是您的函数返回的值。我在我的项目中使用了这段代码,到目前为止还没有问题。


15

虽然不是对整个问题的回答,但我认为值得提一下,在Node.js 10中添加了一个名为isPromise的新实用程序函数,用于检查对象是否为原生Promise:

const utilTypes = require('util').types
const b_Promise = require('bluebird')

utilTypes.isPromise(Promise.resolve(5)) // true
utilTypes.isPromise(b_Promise.resolve(5)) // false

1
这应该是被接受的答案。@theram - HcgRandon

10
如果您正在使用异步方法,可以这样做以避免任何歧义。
async myMethod(promiseOrNot){
  const theValue = await promiseOrNot()
}

如果函数返回的是 Promise,则它将等待并返回已解决的值。如果函数返回一个值,则该值将被视为已解决。
如果函数今天没有返回 Promise,但明天会返回 Promise 或者声明为 async,那么您就具有了未来的兼容性。

1
根据这里的说法,如果被等待的值不是一个Promise,那么[await表达式]会将该值转换为已解决的Promise,并等待它。 - pqnet
1
这基本上就是接受的答案所建议的,只不过这里使用了async-await语法,而不是Promise.resolve() - Felix K.

8

4
为什么我们需要 obj === 'function' 的条件呢? - Maksim Nesterenko
与此答案相同,任何对象都可以有一个名为“then”的方法,因此不能总是将其视为Promise。 - Boghyon Hoffmann

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接