如何判断对象是否为承诺?


335

无论是ES6 Promise还是bluebird Promise,Q Promise等。

如何测试以查看给定对象是否为Promise?


3
充其量您可以检查一种.then方法,但这并不能告诉您您所拥有的绝对是一个承诺。到那时,您所知道的就是您拥有一些可以公开.then方法的东西,例如 Promise。
Scott Offen 2015年

@ScottOffen promise规范没有明确区分。
本杰明·格伦鲍姆

6
我的观点是,任何人都可以创建一个对象,该对象公开一个.then不是Promise 的方法,其行为不像Promise,并且无意像Promise那样使用。检查一个.then方法只是告诉你,如果对象具有一个.then方法,那么你不要有承诺。相反- .then方法的存在意味着您确实有一个承诺-不一定是正确的。
Scott Offen

3
@ScottOffen根据定义,唯一的确定承诺的方法是检查承诺是否具有.then方法。是的,这有可能产生误报,但这是所有promise库都依赖的假设(因为这就是他们可以依赖的全部)。据我所知,唯一的选择就是接受本杰明·格鲁恩鲍姆的建议,并将其贯穿于promise测试套件中。但这对于实际的生产代码不切实际。
JLRishe 2015年

Answers:


342

承诺库如何决定

如果它具有.then功能-这是Promise库唯一使用的标准。

Promises / A +规范中有一个称为then能力的概念,基本上是“带有then方法的对象”。承诺会并且应该使用then方法吸收任何东西。您提到的所有promise实现都可以做到这一点。

如果我们看一下规格

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

它还说明了此设计决策的基本原理:

then只要能够公开Promises / A +兼容then方法,这种对能力的处理就可以使promise实现互操作。它还允许Promises / A +实现使用合理的then方法“整合”不合格的实现。

您应该如何决定

您不应该-而是调用Promise.resolve(x)Q(x)在Q中),该方法将始终将任何值或外部then能力转换为可信任的承诺。比您自己执行这些检查更安全,更轻松。

真的需要确定吗?

您始终可以通过测试套件:D 运行它


168

检查是否可以保证不必要地使代码复杂化,只需使用 Promise.resolve

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

})

1
因此Promise.resolve可以处理所有发生的事情?当然没有什么,但是我想有什么合理的吗?
亚历山大·米尔斯

3
@AlexMills是的,它甚至适用于非标准的Promise,例如jQuery Promise。如果对象的then方法的接口与promise的接口完全不同,则失败。
Esailija '16

19
这个答案虽然可能是很好的建议,但实际上并不能回答问题。
Stijn de Witt

4
除非问题确实与某人实际实现了承诺库有关,否则该问题无效。只有一个Promise库将需要进行检查,之后,您可以始终使用其.resolve方法,如我所示。
Esailija '16

4
@Esalija在我看来,这个问题似乎相关且重要,而不仅仅是承诺库的实现者。对于希望了解实现方式/应该/可能如何运行以及不同的promise库如何彼此交互的promise库的用户,这也很重要。特别是,这个用户对我可以对任何X做出X的承诺这一事实感到非常沮丧,除非X是“承诺”(无论“承诺”在这里意味着什么—就是这个问题),我肯定对此感兴趣确切知道该异常的边界在哪里。
唐·哈奇

103

这是我的原始答案,此后已在规范中批准为测试承诺的方式:

Promise.resolve(obj) == obj

之所以可行,是因为该算法明确要求,Promise.resolve当且仅当它是规范定义中的承诺时,必须返回传入的确切对象。

我在这里有另一个答案,该答案曾经是这样说的,但是当当时Safari无法使用时,我将其更改为其他内容。那是一年前的事,现在即使在Safari中也可以正常使用。

我会编辑我原来的答案,但那感觉不对,因为与现在相比,现在有更多的人投票赞成该答案中更改的解决方案。我相信这是更好的答案,希望您同意。


10
您应该使用===而不是==
尼尔·S

12
对于不同领域的承诺,这也将失败。
本杰明·格伦鲍姆

4
“按规范定义的承诺”的意思是“与通过Promise.resolve()创建的承诺由同一构造函数创建的承诺将是” –因此,它将无法检测到例如。
填充的

3
如果可以通过说明您是如何解释问题而不是立即从一个答案开始而得到改进,那么不幸的是,OP根本没有说清楚,您也没有一个,所以在这一点上OP,作者和读者可能会在3个不同的页面上。您引用的文档说“如果参数是此构造函数产生的承诺”,则斜体部分至关重要。最好指出这就是您要回答的问题。同样,您的答案对于该库的用户有用,但对实现者没有帮助。
唐·哈奇

1
不要使用此方法,这就是@BenjaminGruenbaum所说的更多原因。gist.github.com/reggi/a1da4d0ea4f1320fa15405fb86358cff
ThomasReggi,

61

更新:这不再是最佳答案。请改选我的其他答案

obj instanceof Promise

应该这样做。请注意,这只能与本机es6 promises一起可靠地工作。

如果您使用的是填充程序,promise库或其他任何看起来像promise一样的东西,那么测试“ thenable”(使用.then方法的任何东西)可能更合适,如此处其他答案所示。


从那以后,我已经指出Promise.resolve(obj) == obj在Safari 中无法使用。使用instanceof Promise代替。
三角帆

2
这不能可靠地工作,并导致我难以跟踪问题。假设您有一个使用es6.promise填充程序的库,并且在某处使用了Bluebird,则将遇到问题。我在Chrome Canary中遇到了这个问题。
vaughan

1
是的,这个答案实际上是错误的。我到这里来是因为很难解决这个问题。实际上obj && typeof obj.then == 'function',您应该检查一下,因为它可以与所有类型的Promise一起使用,并且实际上是规范建议的,实现/ polyfills使用的方式。Promise.all例如,本地人将适用于所有能力then,而不仅仅是其他本地人的诺言。您的代码也应该如此。所以instanceof Promise不是一个好的解决方案。
Stijn de Witt

2
后续操作-更糟糕的是:在node.js 6.2.2上,仅使用本机Promise我现在正在尝试调试console.log(typeof p, p, p instanceof Promise);产生此输出的问题:object Promise { <pending> } false。如您所见,这是一个很好的承诺-但是instanceof Promise测试返回了false吗?
Mörre

2
对于不同领域的承诺,这将失败。
本杰明·格伦鲍姆

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

6
如果事物未定义怎么办?您需要通过事物&&来防范这一点
mrBorna '17

并非最佳,但绝对有可能;也取决于问题的范围。防御性地编写100%通常适用于开放式公共API,或者您知道数据的形状/签名是完全开放式的。
rob2d '18年

17

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

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

Call荷兰国际集团toString直接从Object.prototype返回一个原生字符串表示,其是给定对象类型的"[object Promise]"在我们的例子。这样可以确保给定的对象

  • 绕过误报,例如..:
    • 具有相同构造函数名称的自定义对象类型(“ Promise”)。
    • toString给定对象的自写方法。
  • instanceof相比,可跨多个环境上下文(例如iframe)工作isPrototypeOf

但是,任何通过标签修改了标签的特定宿主对象都可以返回。取决于项目,这可能是预期结果,也可能不是预期结果(例如,是否有自定义的Promise实现)。Symbol.toStringTag"[object 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 
}

根据这个这个部分的规范的,函数的字符串表示应该是:

“函数标识符FormalParameterList opt){ FunctionBody }”

上面已对此进行了相应处理。该函数体[native code]在所有主流浏览器。

MDN: Function.prototype.toString

这也适用于多种环境。


12

并非完整问题的答案,但我认为值得一提的是,在Node.js 10 isPromise中添加了一个名为util的新util函数,该函数可检查对象是否为本地Promise:

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

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

11

这是graphql-js包检测promise的方式:

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

value是函数的返回值。我正在项目中使用此代码,到目前为止没有问题。



6

如果您使用的是Typescript,我想补充一点,您可以使用“类型谓词”功能。只需将逻辑验证包装在返回的函数中x is Promise<any>,您就不需要进行类型转换。在我的示例下面,c是一个Promise或我想通过调用该c.fetch()方法转换为Promise的一种类型。

export function toPromise(c: Container<any> | Promise<any>): Promise<any> {
    if (c == null) return Promise.resolve();
    return isContainer(c) ? c.fetch() : c;
}

export function isContainer(val: Container<any> | Promise<any>): val is Container<any> {
    return val && (<Container<any>>val).fetch !== undefined;
}

export function isPromise(val: Container<any> | Promise<any>): val is Promise<any> {
    return val && (<Promise<any>>val).then !== undefined;
}

更多信息:https : //www.typescriptlang.org/docs/handbook/advanced-types.html


6

如果您使用的是异步方法,则可以这样做并避免任何歧义。

async myMethod(promiseOrNot){
  const theValue = await promiseOrNot()
}

如果函数返回promise,它将等待并返回已解析的值。如果该函数返回一个值,它将被视为已解析。

如果该函数今天没有返回承诺,但是明天返回了一个承诺或被声明为异步,则您将具有未来的能力。


根据这里的方法,这是
可行的

基本上是已接受的答案中所建议的内容,除了此处使用async-await语法代替Promise.resolve()
B12Toaster

3
it('should return a promise', function() {
    var result = testedFunctionThatReturnsPromise();
    expect(result).toBeDefined();
    // 3 slightly different ways of verifying a promise
    expect(typeof result.then).toBe('function');
    expect(result instanceof Promise).toBe(true);
    expect(result).toBe(Promise.resolve(result));
});

2

我将此函数用作通用解决方案:

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

-1

在寻找一种检测异步功能甚至Promises可靠方法后,我最终使用以下测试:

() => fn.constructor.name === 'Promise' || fn.constructor.name === 'AsyncFunction'

如果您子类化Promise并创建该实例,则此测试可能会失败。不过,这应该可以在您要测试的大部分内容中发挥作用。
Theram

同意,但我不明白为什么有人会创建承诺的副内容
Sebastien H.

fn.constructor.name === 'AsyncFunction'是错误的-这意味着某事是异步函数而不是诺言-也不能保证它能正常工作,因为人们可以
继承

@BenjaminGruenbaum上面的示例在大多数情况下适用,如果您创建自己的子类,则应在其名称上添加测试
Sebastien

可以,但是如果您已经知道存在哪些对象,那么您已经知道东西是否是承诺。
本杰明·格伦鲍姆

-3

ES6:

const promise = new Promise(resolve => resolve('olá'));

console.log(promise.toString().includes('Promise')); //true

2
任何具有(或已被覆盖)toString方法的对象都可以返回包含的字符串"Promise"
Boghyon Hoffmann '18

4
这个答案有很多原因,是不好的,最明显的是'NotAPromise'.toString().includes('Promise') === true
该死的
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.