Como posso saber se um objeto é uma promessa?

336

Seja uma Promessa ES6 ou Promessa bluebird, Q Promise, etc.

Como faço para testar se um determinado objeto é uma promessa?

o carneiro
fonte
3
Na melhor das hipóteses, você poderia procurar um .thenmétodo, mas isso não lhe diria que o que você tem é uma promessa definitivamente. Tudo o que você saberia naquele momento é que você tem algo que expõe um .thenmétodo, como uma Promessa.
Scott Offen
@ScottOffen a especificação da promessa explicitamente não faz distinção.
Benjamin Gruenbaum
6
Meu argumento é que qualquer pessoa pode criar um objeto que exponha um .thenmétodo que não seja uma promessa, não se comporte como uma promessa e não tenha intenção de ser usado como uma promessa. A verificação de um .thenmétodo apenas informa que o objeto if não possui um .thenmétodo, então você não tem uma Promessa. O inverso - que a existência de um .thenmeio de métodos que você faz tem uma promessa - não é necessariamente verdade.
Scott Offen
3
@ ScottOffen Por definição, a única maneira estabelecida de identificar uma promessa é verificar se ela possui um .thenmétodo. Sim, isso tem potencial para falsos positivos, mas é a suposição de que todas as bibliotecas promissoras dependem (porque é nisso que elas podem confiar). A única alternativa, até onde posso ver, é aceitar a sugestão de Benjamin Gruenbaum e executá-la no conjunto de testes promissores. Mas isso não é prático para o código de produção real.
JLRishe

Respostas:

342

Como uma biblioteca de promessas decide

Se tiver uma .thenfunção - esse é o única padrão das bibliotecas de promessas.

A especificação Promises / A + possui uma noção chamada thencapaz, que é basicamente "um objeto com um thenmétodo". Promessas vão e devem assimilar qualquer coisa com um método then. Toda a implementação da promessa que você mencionou faz isso.

Se olharmos para a especificação :

2.3.3.3 se thenfor uma função, chame-a com x como este, primeiro argumento resolvePromise e segundo argumento rejectPromise

Também explica a justificativa para esta decisão de design:

Esse tratamento de capacidades thenpermite que as implementações de promessa interoperem, desde que exponham um thenmétodo compatível com Promises / A + . Ele também permite que as implementações do Promises / A + “assimilem” as implementações não conformes com métodos razoáveis.

Como você deve decidir

Você não deve - em vez disso, ligar Promise.resolve(x)( Q(x)em Q) que sempre converterá qualquer valor ou thencapacidade externa em uma promessa confiável. É mais seguro e fácil do que executar essas verificações você mesmo.

realmente precisa ter certeza?

Você sempre pode executá-lo no conjunto de testes : D

Benjamin Gruenbaum
fonte
168

Para verificar se algo é promissor, complica desnecessariamente o código, basta usar Promise.resolve

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

})
Esailija
fonte
1
para que Promise.resolve possa lidar com qualquer coisa que aparecer no seu caminho? Certamente não é nada, mas acho que algo razoável?
Alexander Mills
3
@AlexMills sim, funciona até para promessas não padrão, como a jQuery. Pode falhar se o objeto tiver um método then que possui uma interface completamente diferente da promessa então.
Esailija
19
Essa resposta, embora talvez seja um bom conselho, na verdade não responde à pergunta.
Stijn de Witt
4
A menos que a pergunta seja realmente sobre alguém realmente implementando uma biblioteca de promessas, a pergunta é inválida. Somente uma biblioteca de promessas precisaria fazer a verificação; depois disso, você sempre poderá usar o método .resolve como mostrei.
Esailija 16/03/16
4
@Esalija A pergunta me parece relevante e importante, não apenas para um implementador de uma biblioteca de promessas. Também é relevante para um usuário de uma biblioteca de promessas que deseja saber como as implementações devem / devem / podem se comportar e como diferentes bibliotecas de promessas interagem entre si. Em particular, esse usuário está muito consternado pelo fato aparente de que eu posso fazer uma promessa de um X para qualquer X, exceto quando X é "promessa" (qualquer que seja "promessa" significa aqui - eis a questão), e estou definitivamente interessado em saber exatamente onde estão os limites dessa exceção.
Don escotilha
104

Aqui está a minha resposta original, que foi ratificada na especificação como a maneira de testar uma promessa:

Promise.resolve(obj) == obj

Isso funciona porque o algoritmo exige explicitamente que Promise.resolvedeve retornar o objeto exato passado se e somente se for uma promessa pela definição da especificação.

Tenho outra resposta aqui, que costumava dizer isso, mas mudei para outra coisa quando não funcionava com o Safari naquele momento. Isso foi há um ano e agora isso funciona de maneira confiável, mesmo no Safari.

Eu teria editado minha resposta original, exceto a que parecia errada, já que agora mais pessoas votaram na solução alterada nessa resposta do que a original. Acredito que esta seja a melhor resposta e espero que você concorde.

jib
fonte
10
você deve usar em ===vez de ==?
Neil S
12
Isso também falhará em promessas que não são do mesmo reino.
Benjamin Gruenbaum
4
"uma promessa pela definição da especificação" parece significar "uma promessa criada pelo mesmo construtor que uma promessa criada por meio de Promise.resolve () seria" - então isso falhará em detectar se, por exemplo. uma promessa polyfilled é realmente uma promessa
VoxPelli
3
Essa resposta pode ser melhorada se começar dizendo como você está interpretando a pergunta, em vez de começar imediatamente com uma resposta - o OP infelizmente ainda não deixou claro, e você também não o fez. o OP, o escritor e o leitor provavelmente estão em 3 páginas diferentes. O documento a que você se refere diz "se o argumento é uma promessa produzida por esse construtor ", sendo a parte em itálico crucial. Seria bom afirmar que essa é a pergunta que você está respondendo. Além disso, sua resposta é útil para um usuário desta biblioteca, mas não para o implementador.
Don Hatch
1
Não use esse método, eis o motivo, mais ao ponto de @ BenjaminGruenbaum. gist.github.com/reggi/a1da4d0ea4f1320fa15405fb86358cff
ThomasReggi
61

Atualização: essa não é mais a melhor resposta. Por favor vote em minha outra resposta .

obj instanceof Promise

deve fazê-lo. Observe que isso só pode funcionar de maneira confiável com as promessas nativas do es6.

Se você estiver usando um calço, uma biblioteca de promessas ou qualquer outra coisa que pretenda ser semelhante a uma promessa, pode ser mais apropriado testar um "selecionável" (qualquer coisa com um .thenmétodo), conforme mostrado em outras respostas aqui.

jib
fonte
Desde então, me foi indicado que Promise.resolve(obj) == objnão funcionará no Safari. Use em instanceof Promisevez disso.
jib
2
Isso não funciona de maneira confiável e me causou um problema insanamente difícil de rastrear. Digamos que você tenha uma biblioteca que use o calço es6.promise e use o Bluebird em algum lugar, você terá problemas. Este problema surgiu para mim no Chrome Canary.
58568
1
Sim, esta resposta está realmente errada. Acabei aqui por exatamente um problema tão difícil de rastrear. Você realmente deve verificar obj && typeof obj.then == 'function', porque ele funcionará com todos os tipos de promessas e é realmente o caminho recomendado pelas especificações e usado pelas implementações / polyfills. O nativo, Promise.allpor exemplo, funcionará em todas as thenhabilidades, não apenas em outras promessas nativas. O mesmo deve acontecer com o seu código. Portanto, instanceof Promisenão é uma boa solução.
Stijn de Witt
2
Dar seguimento - é pior: Em Node.js 6.2.2 usando apenas promessas nativas Estou agora tentando depurar um problema onde console.log(typeof p, p, p instanceof Promise);produz esta saída: object Promise { <pending> } false. Como você pode ver, é uma promessa certa - e ainda assim o instanceof Promiseteste retorna false?
22416
2
Isso falhará em promessas que não são do mesmo reino.
Benjamin Gruenbaum
46
if (typeof thing.then === 'function') {
    // probably a promise
} else {
    // definitely not a promise
}
unobf
fonte
6
e se a coisa não estiver definida? você precisa para se proteger contra essa via coisa && ...
mrBorna
não é o melhor, mas é definitivamente muito provável; depende também do escopo do problema. Escrever 100% na defensiva geralmente é aplicável em APIs públicas abertas ou onde você sabe que a forma / assinatura dos dados é completamente aberta.
Rob2d 01/05/19
17

Para ver se o objeto fornecido é uma promessa do ES6 , podemos fazer uso deste predicado:

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

Calling toStringdiretamente do Object.prototyperetorna uma representação de string nativa do tipo de objeto especificado, que está "[object Promise]"em nosso caso. Isso garante que o objeto fornecido

  • Ignora falsos positivos, como ..:
    • Tipo de objeto auto-definido com o mesmo nome do construtor ("Promise").
    • toStringMétodo auto-escrito do objeto fornecido.
  • Funciona em vários contextos de ambiente (por exemplo, iframes) em contraste cominstanceof ou isPrototypeOf.

No entanto, qualquer objeto host específico , que tenha sua tag modificada porSymbol.toStringTag , pode retornar "[object Promise]". Este pode ser o resultado pretendido ou não, dependendo do projeto (por exemplo, se houver uma implementação personalizada do Promise).


Para ver se o objeto é de uma promessa nativa do ES6 , 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 acordo com esta e esta seção da especificação, a representação de string da função deve ser:

" identificador da função ( formalParameterList opt ) { FunctionBody }"

que é tratado em conformidade acima. O FunctionBody está [native code]em todos os principais navegadores.

MDN: Function.prototype.toString

Isso funciona em vários contextos de ambiente também.

Boghyon Hoffmann
fonte
12

Não é uma resposta para a pergunta completa, mas acho que vale a pena mencionar que no Node.js. 10 isPromisefoi adicionada uma nova função util chamada que verifica se um objeto é uma promessa nativa ou não:

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

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

É assim que o pacote graphql-js detecta promessas:

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

valueé o valor retornado da sua função. Estou usando esse código no meu projeto e não tenho nenhum problema até agora.

muratgozel
fonte
6

Aqui está o formulário 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';

se um objeto com um thenmétodo, ele deve ser tratado como um Promise.

ssnau
fonte
3
por que precisamos da condição obj === 'function' btw?
precisa saber é o seguinte
Mesmo que esta resposta , qualquer objeto pode ter um método "então" e, portanto, nem sempre pode ser tratado como uma promessa.
Boghyon Hoffmann 27/03
6

Caso você esteja usando o Typecript , gostaria de acrescentar que você pode usar o recurso "predicado de tipo". Basta envolver a verificação lógica em uma função que retorne x is Promise<any>e você não precisará fazer previsões de digitação. Abaixo, no meu exemplo, chá uma promessa ou um dos meus tipos que desejo converter em promessa chamando o 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;
}

Mais informações: https://www.typescriptlang.org/docs/handbook/advanced-types.html

Murilo Perrone
fonte
6

Se você estiver em um método assíncrono, poderá fazer isso e evitar qualquer ambiguidade.

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

Se a função retornar promessa, aguardará e retornará com o valor resolvido. Se a função retornar um valor, ela será tratada como resolvida.

Se a função não retornar uma promessa hoje, mas amanhã retornar uma ou for declarada assíncrona, você estará à prova de futuro.

Steven Spungin
fonte
isso funciona, de acordo com o aqui : "se o valor [esperado] não é uma promessa, [a expressão await] converte o valor para um Promise resolvido, e espera por isso"
pqnet
É basicamente o que foi sugerido na resposta aceita, exceto que a sintaxe async- Promise.resolve()
waitit
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));
});
repolho roxo
fonte
2

Eu uso essa função como uma solução universal:

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

depois de procurar uma maneira confiável de detectar funções assíncronas ou até promessas , acabei usando o seguinte teste:

() => fn.constructor.name === 'Promise' || fn.constructor.name === 'AsyncFunction'
Sebastien H.
fonte
se você subclassificar Promisee criar instâncias disso, esse teste poderá falhar. isso deve funcionar para a maioria do que você está tentando testar.
Theram
Concordou, mas não vejo por que alguém iria criar sublasses de promessas
Sebastien H.
fn.constructor.name === 'AsyncFunction'está errado - isso significa alguma coisa é uma função assíncrona e não uma promessa - também não é garantido para o trabalho porque as pessoas podem subclasse promessas
Benjamin Gruenbaum
@BenjaminGruenbaum Os exemplos de obras acima na maioria dos casos, se você criar sua própria subclasse você deve adicionar os testes em seu nome
Sebastien H.
Você pode, mas se você já sabe quais objetos existem, já sabe se as coisas são promessas ou não.
Benjamin Gruenbaum
-3

ES6:

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

console.log(promise.toString().includes('Promise')); //true
Mathias Gheno Azzolini
fonte
2
Qualquer objeto que tenha (ou tenha sobrescrito) o toStringmétodo pode simplesmente retornar uma string que inclui "Promise".
Boghyon Hoffmann
4
Esta resposta é ruim para muitas razões, sendo a mais óbvia'NotAPromise'.toString().includes('Promise') === true
DAMD