¿Cómo puedo saber si un objeto es una promesa?

336

Ya sea una promesa ES6 o una promesa Bluebird, Q Promise, etc.

¿Cómo pruebo para ver si un objeto dado es una promesa?

el carnero
fuente
3
En el mejor de los casos, podría buscar un .thenmétodo, pero eso no le diría que lo que tiene es una Promesa definitivamente. Todo lo que sabría en ese momento es que tiene algo que expone un .thenmétodo, como una Promesa.
Scott Offen
@ScottOffen la especificación de promesa explícitamente no hace una distinción.
Benjamin Gruenbaum
66
Mi punto es que cualquiera puede crear un objeto que exponga un .thenmétodo que no sea una Promesa, no se comporte como una Promesa y no tuviera la intención de ser utilizado como una Promesa. Buscar un .thenmétodo solo te dice que si el objeto no tiene un .thenmétodo, entonces no tienes una Promesa. La inversa - que la existencia de unos .thenmedios método que haces tiene una promesa - no es necesariamente cierto.
Scott Offen
3
@ScottOffen Por definición, la única forma establecida de identificar una promesa es verificar si tiene un .thenmétodo. Sí, eso tiene el potencial de falsos positivos, pero se supone que todas las bibliotecas prometedoras dependen (porque eso es todo en lo que pueden confiar). La única alternativa hasta donde puedo ver es tomar la sugerencia de Benjamin Gruenbaum y ejecutarla a través del conjunto de pruebas de promesa. Pero eso no es práctico para el código de producción real.
JLRishe

Respuestas:

342

Cómo decide una biblioteca de promesas

Si tiene una .thenfunción, esa es la única promesa estándar que usan las bibliotecas.

La especificación Promesas / A + tiene una noción llamada thencapaz que es básicamente "un objeto con un thenmétodo". Las promesas harán y deberían asimilar cualquier cosa con un método de entonces. Toda la implementación de promesa que ha mencionado hace esto.

Si nos fijamos en la especificación :

2.3.3.3 si thenes una función, llámela con x como esto, primer argumento resolvePromise y segundo argumento acceptPromise

También explica la justificación de esta decisión de diseño:

Este tratamiento de thenables permite que las implementaciones de promesas interoperen, siempre que expongan un thenmétodo compatible con Promesas / A + . También permite que las implementaciones de Promises / A + "asimilen" implementaciones no conformes con métodos razonables.

Cómo deberías decidir

No debería, en su lugar, llamar Promise.resolve(x)( Q(x)en Q) que siempre convertirá cualquier valor o thencapacidad externa en una promesa confiable. Es más seguro y fácil que realizar estas comprobaciones usted mismo.

realmente necesita estar seguro?

Siempre puede ejecutarlo a través del conjunto de pruebas : D

Benjamin Gruenbaum
fuente
168

Comprobar si algo es prometedor complica innecesariamente el código, solo use Promise.resolve

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

})
Esailija
fuente
1
¿Entonces Promise.resolve puede manejar todo lo que se le presente? Seguramente no nada, pero supongo que algo razonable.
Alexander Mills el
3
@AlexMills sí, incluso funciona para promesas no estándar como jQuery promise. Puede fallar si el objeto tiene un método de entonces que tiene una interfaz completamente diferente de la promesa de entonces.
Esailija
19
Esta respuesta, aunque quizás sea un buen consejo, en realidad no responde la pregunta.
Stijn de Witt
44
A menos que la pregunta sea realmente sobre alguien que realmente implemente una biblioteca de promesas, la pregunta no es válida. Solo una biblioteca prometedora necesitaría hacer la verificación, después de eso siempre puedes usar su método .resolve como lo mostré.
Esailija
44
@Esalija La pregunta me parece relevante e importante, no solo para un implementador de una biblioteca prometedora. También es relevante para un usuario de una biblioteca de promesas que quiere saber cómo se comportarán / deberían / ​​podrían las implementaciones y cómo interactuarán entre sí las diferentes bibliotecas de promesas. En particular, este usuario está muy consternado por el hecho aparente de que puedo hacer una promesa de una X para cualquier X, excepto cuando X es "promesa" (lo que sea que significa "promesa" aquí, esa es la pregunta), y definitivamente estoy interesado en saber exactamente dónde están los límites de esa excepción.
Don Hatch
104

Aquí está mi respuesta original, que desde entonces ha sido ratificada en la especificación como la forma de probar una promesa:

Promise.resolve(obj) == obj

Esto funciona porque el algoritmo exige explícitamente que Promise.resolvedebe devolver el objeto exacto pasado si y solo si es una promesa según la definición de la especificación.

Tengo otra respuesta aquí, que solía decir esto, pero la cambié a otra cuando no funcionaba con Safari en ese momento. Eso fue hace un año, y ahora funciona de manera confiable incluso en Safari.

Habría editado mi respuesta original, excepto que se sintió mal, dado que ahora más personas han votado por la solución alterada en esa respuesta que la original. Creo que esta es la mejor respuesta, y espero que esté de acuerdo.

foque
fuente
10
deberías usar en ===lugar de ==?
Neil S
12
Esto también fallará para las promesas que no son del mismo reino.
Benjamin Gruenbaum
44
"una promesa según la definición de la especificación" parece significar "una promesa creada por el mismo constructor que una promesa creada a través de Promise.resolve () sería", por lo que no se detectará si, por ejemplo. una promesa cumplida es en realidad una promesa
VoxPelli
3
Esta respuesta podría mejorarse si comenzara declarando cómo está interpretando la pregunta en lugar de comenzar con una respuesta de inmediato; desafortunadamente, el OP no lo ha dejado del todo claro, y usted tampoco lo ha hecho, por lo que en este punto El OP, el escritor y el lector probablemente estén en 3 páginas diferentes. El documento al que se refiere dice "si el argumento es una promesa producida por este constructor ", la parte en cursiva es crucial. Sería bueno decir que esa es la pregunta que está respondiendo. Además, su respuesta es útil para un usuario de esta biblioteca pero no para el implementador.
Don Hatch
1
No use este método, aquí está el por qué, más al punto de @ BenjaminGruenbaum. gist.github.com/reggi/a1da4d0ea4f1320fa15405fb86358cff
ThomasReggi
61

Actualización: esta ya no es la mejor respuesta. Por favor vote mi otra respuesta en su lugar.

obj instanceof Promise

Deberías hacerlo. Tenga en cuenta que esto solo puede funcionar de manera confiable con las promesas nativas de es6.

Si está utilizando una cuña, una biblioteca de promesas o cualquier otra cosa que pretenda ser como una promesa, entonces puede ser más apropiado hacer una prueba para un "thenable" (cualquier cosa con un .thenmétodo), como se muestra en otras respuestas aquí.

foque
fuente
Desde entonces me han señalado que Promise.resolve(obj) == objno funcionará en Safari. Usar en su instanceof Promiselugar.
jib
2
Esto no funciona de manera confiable y me causó un problema increíblemente difícil de rastrear. Supongamos que tiene una biblioteca que usa la cuña es6.promise y usa Bluebird en algún lugar, tendrá problemas. Este problema se me ocurrió en Chrome Canary.
vaughan
1
Sí, esta respuesta es realmente incorrecta. Terminé aquí por exactamente un problema tan difícil de rastrear. En realidad, debería verificarlo obj && typeof obj.then == 'function', porque funcionará con todo tipo de promesas y en realidad es la forma recomendada por la especificación y utilizada por las implementaciones / polyfills. Native, Promise.allpor ejemplo, funcionará en todas las thencapacidades, no solo en otras promesas nativas. También debería tu código. Entonces instanceof Promiseno es una buena solución.
Stijn de Witt
2
Seguimiento - es peor: En Node.JS 6.2.2 usando sólo promesas nativos que en este momento estoy tratando de depurar un problema en el que console.log(typeof p, p, p instanceof Promise);produce este resultado: object Promise { <pending> } false. Como puede ver, es una promesa, ¿y la instanceof Promiseprueba vuelve false?
Mörre
2
Esto fallará para las promesas que no son del mismo reino.
Benjamin Gruenbaum
46
if (typeof thing.then === 'function') {
    // probably a promise
} else {
    // definitely not a promise
}
unobf
fuente
66
¿Qué pasa si la cosa no está definida? que necesita para evitar que a través de lo && ...
mrBorna
no es lo mejor, pero definitivamente es muy probable; depende también del alcance del problema. Escribir 100% a la defensiva generalmente es aplicable en API públicas abiertas o donde se sabe que la forma / firma de los datos es completamente abierta.
rob2d
17

Para ver si el objeto dado es una promesa ES6 , podemos hacer uso de este predicado:

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

Calling toStringdirectamente del Object.prototypedevuelve una representación de cadena nativa del tipo de objeto dado que es "[object Promise]"en nuestro caso. Esto asegura que el objeto dado

  • Omite falsos positivos como ..:
    • Tipo de objeto autodefinido con el mismo nombre de constructor ("Promesa").
    • toStringMétodo auto-escrito del objeto dado.
  • Funciona en múltiples contextos de entorno (por ejemplo, iframes) en contraste coninstanceof o isPrototypeOf.

Sin embargo, cualquier objeto host particular , que tenga su etiqueta modificada a través deSymbol.toStringTag , puede regresar "[object Promise]". Este puede ser el resultado deseado o no dependiendo del proyecto (por ejemplo, si hay una implementación personalizada de Promise).


Para ver si el objeto es de una Promesa ES6 nativa , podemos usar:

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 
}

De acuerdo con esta y esta sección de la especificación, la representación de cadena de la función debe ser:

" Identificador de función ( FormalParameterList opt ) { FunctionBody }"

que se maneja en consecuencia arriba. El FunctionBody es [native code]en todos los principales navegadores.

MDN: Function.prototype.toString

Esto también funciona en múltiples contextos de entorno.

Boghyon Hoffmann
fuente
12

No es una respuesta a la pregunta completa, pero creo que vale la pena mencionar que en Node.js 10 isPromisese agregó una nueva función de utilidad llamada que comprueba si un objeto es una Promesa nativa o no:

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

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

Así es como el paquete graphql-js detecta las promesas:

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

valuees el valor devuelto de su función. Estoy usando este código en mi proyecto y no tengo ningún problema hasta ahora.

muratgozel
fuente
6

Aquí está el formulario de código https://github.com/ssnau/xkit/blob/master/util/is-promise.js

!!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';

si un objeto con un thenmétodo, debe tratarse como a Promise.

ssnau
fuente
3
¿Por qué necesitamos obj === 'función' condición por cierto?
Alendorff
Al igual que esta respuesta , cualquier objeto puede tener un método "entonces" y, por lo tanto, no siempre se puede tratar como una promesa.
Boghyon Hoffmann
6

En caso de que esté utilizando Typecript , me gustaría agregar que puede usar la función "predicado de tipo". Solo debe envolver la verificación lógica en una función que regrese x is Promise<any>y no necesitará hacer typecasts. A continuación, en mi ejemplo, chay una promesa o uno de mis tipos que quiero convertir en una promesa llamando al c.fetch()método.

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;
}

Más información: https://www.typescriptlang.org/docs/handbook/advanced-types.html

Murilo Perrone
fuente
6

Si utiliza un método asíncrono, puede hacerlo y evitar cualquier ambigüedad.

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

Si la función devuelve la promesa, esperará y volverá con el valor resuelto. Si la función devuelve un valor, se tratará como resuelto.

Si la función no devuelve una promesa hoy, pero mañana devuelve una o se declara asíncrona, estará preparado para el futuro.

Steven Spungin
fuente
esto funciona, según aquí : "si el valor [esperado] no es una promesa, [la expresión de espera] convierte el valor en una Promesa resuelta y lo espera"
pqnet
Básicamente es lo que se ha sugerido en la respuesta aceptada, excepto que aquí se usa la sintaxis async- Promise.resolve()
wait en
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));
});
repollo morado
fuente
2

Yo uso esta función como una solución universal:

function isPromise(value) {
  return value && value.then && typeof value.then === 'function';
}
safrazik
fuente
-1

Después de buscar una forma confiable de detectar funciones asincrónicas o incluso promesas , terminé usando la siguiente prueba:

() => fn.constructor.name === 'Promise' || fn.constructor.name === 'AsyncFunction'
Sebastien H.
fuente
si subclasifica Promisey crea instancias de eso, esta prueba puede fallar. Sin embargo, esto debería funcionar para la mayoría de lo que estás tratando de probar.
Theram
De acuerdo, pero no veo por qué alguien crearía subclases de promesas
Sebastien H.
fn.constructor.name === 'AsyncFunction'está mal, significa que algo es una función asincrónica y no una promesa, tampoco se garantiza que funcione porque la gente puede subclasificar las promesas
Benjamin Gruenbaum
@BenjaminGruenbaum El ejemplo anterior funciona en la mayoría de los casos, si crea su propia subclase, debe agregar las pruebas en su nombre
Sebastien H.
Puede, pero si ya sabe qué objetos hay, ya sabe si las cosas son promesas o no.
Benjamin Gruenbaum
-3

ES6:

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

console.log(promise.toString().includes('Promise')); //true
Mathias Gheno Azzolini
fuente
2
Cualquier objeto que tenga (o haya sobrescrito) el toStringmétodo puede devolver una cadena que incluya "Promise".
Boghyon Hoffmann el
44
Esta respuesta es mala por muchas razones, siendo la más obvia'NotAPromise'.toString().includes('Promise') === true
maldita sea