Come faccio a sapere se un oggetto è una promessa?


336

Che si tratti di una promessa ES6 o di una promessa bluebird, una promessa Q, ecc.

Come posso verificare se un determinato oggetto è una promessa?


3
Nella migliore delle ipotesi potresti verificare un .thenmetodo, ma ciò non ti direbbe che ciò che hai è una Promessa definitivamente. Tutto quello che sapresti a quel punto è che hai qualcosa che espone un .thenmetodo, come una Promessa.
Scott Offen,

@ScottOffen la specifica della promessa esplicitamente non fa distinzioni.
Benjamin Gruenbaum,

6
Il mio punto è che chiunque può creare un oggetto che espone un .thenmetodo che non è una Promessa, non si comporta come una Promessa e non aveva intenzione di essere usato come una Promessa. Il controllo di un .thenmetodo ti dice solo che l'oggetto if non ha un .thenmetodo, quindi non hai una Promessa. L'inverso - che l'esistenza di un .thenmezzo metodo che si fa hanno una promessa - non è necessariamente vero.
Scott Offen,

3
@ScottOffen Per definizione, l' unico modo stabilito per identificare una promessa è verificare se ha un .thenmetodo. Sì, questo ha il potenziale per falsi positivi, ma è il presupposto che tutte le biblioteche promesse facciano affidamento (perché è tutto ciò su cui possono contare). L'unica alternativa per quanto posso vedere è prendere il suggerimento di Benjamin Gruenbaum ed eseguirlo attraverso la suite di test promettenti. Ma questo non è pratico per il vero codice di produzione.
JLRishe

Risposte:


342

Come decide una biblioteca promessa

Se ha una .thenfunzione, questa è l' unica libreria standard utilizzata.

La specifica Promises / A + ha una nozione chiamata thencapace che è sostanzialmente "un oggetto con un thenmetodo". Le promesse saranno e dovrebbero assimilare qualsiasi cosa con un metodo allora. Tutte le implementazioni promesse che hai citato fanno questo.

Se guardiamo le specifiche :

2.3.3.3 se thenè una funzione, chiamala con x come questa, primo argomento resolPromise e secondo argomento rejectPromise

Spiega anche la logica di questa decisione progettuale:

Questo trattamento di thenables consente alle implementazioni promettenti di interagire, purché espongano un thenmetodo conforme a Promises / A + . Inoltre, consente alle implementazioni Promises / A + di "assimilare" le implementazioni non conformi con metodi ragionevoli.

Come dovresti decidere

Non dovresti - invece chiama Promise.resolve(x)( Q(x)in Q) che convertirà sempre qualsiasi valore o valore esterno thenin una promessa fidata. È più sicuro e più semplice che eseguire questi controlli da soli.

hai davvero bisogno di essere sicuro?

Puoi sempre eseguirlo attraverso la suite di test : D


168

Controllare se qualcosa è promettente complica inutilmente il codice, basta usare Promise.resolve

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

})

1
così Promise.resolve può gestire tutto ciò che gli si presenta? Sicuramente non c'è niente, ma immagino qualcosa di ragionevole?
Alexander Mills,

3
@AlexMills sì, funziona anche con promesse non standard come jQuery promise. Può fallire se l'oggetto ha un metodo allora che ha un'interfaccia completamente diversa dalla promessa allora.
Esailija,

19
Questa risposta, sebbene forse un buon consiglio, in realtà non risponde alla domanda.
Stijn de Witt,

4
A meno che la domanda non riguardi davvero qualcuno che sta effettivamente implementando una libreria di promesse, la domanda non è valida. Solo una libreria promessa dovrebbe fare il controllo, dopo di che puoi sempre usare il suo metodo .resolve come ho mostrato.
Esailija,

4
@Esalija La domanda mi sembra pertinente e importante, non solo per un implementatore di una biblioteca di promesse. È inoltre rilevante per un utente di una biblioteca di promesse che desidera sapere come si comportano / dovrebbero / potrebbero implementare le implementazioni e in che modo le diverse biblioteche di promesse interagiranno tra loro. In particolare, questo utente è fortemente sgomento per il fatto evidente che posso fare una promessa di una X per qualsiasi X tranne quando X è "promessa" (qualunque cosa "promessa" significhi qui-- questa è la domanda), e sono decisamente interessato nel sapere esattamente dove si trovano i confini di tale eccezione.
Don Hatch,

104

Ecco la mia risposta originale, che da allora è stata ratificata nelle specifiche come il modo per testare una promessa:

Promise.resolve(obj) == obj

Questo funziona perché l' algoritmo richiede esplicitamente che Promise.resolvedeve restituire l'oggetto esatto passato se e solo se è una promessa dalla definizione della specifica.

Ho un'altra risposta qui, che era solito dire questo, ma l'ho cambiata in qualcos'altro quando non funzionava con Safari in quel momento. È stato un anno fa e ora funziona in modo affidabile anche in Safari.

Avrei modificato la mia risposta originale, tranne che mi sembrava sbagliato, dato che ormai più persone hanno votato per la soluzione modificata in quella risposta rispetto all'originale. Credo che questa sia la risposta migliore e spero che tu sia d'accordo.


10
dovresti usare ===invece di ==?
Neil S

12
Ciò fallirà anche per le promesse che non sono dello stesso regno.
Benjamin Gruenbaum,

4
"una promessa secondo la definizione della specifica" sembra essere "una promessa creata dallo stesso costruttore come una promessa creata tramite Promise.resolve () sarebbe" - quindi questo non riuscirà a rilevare se ad es. una Promessa
polifillata

3
Questa risposta potrebbe essere migliorata se iniziasse affermando come stai interpretando la domanda piuttosto che iniziare subito con una risposta - l'OP purtroppo non l'ha resa affatto chiara, e nemmeno tu, quindi a questo punto l'OP, lo scrittore e il lettore sono probabilmente su 3 pagine diverse. Il documento a cui ti riferisci dice "se l'argomento è una promessa prodotta da questo costruttore ", la parte in corsivo è cruciale. Sarebbe bene affermare che questa è la domanda a cui stai rispondendo. Inoltre, la tua risposta è utile per un utente di questa libreria ma non per l'implementatore.
Don Hatch,

1
Non usare questo metodo, ecco perché, più al punto di @ BenjaminGruenbaum. gist.github.com/reggi/a1da4d0ea4f1320fa15405fb86358cff
ThomasReggi

61

Aggiornamento: questa non è più la risposta migliore. Per favore, vota l'altra mia risposta .

obj instanceof Promise

dovrebbe farlo. Nota che questo può funzionare in modo affidabile solo con le promesse es6 native.

Se stai usando uno shim, una libreria di promesse o qualsiasi altra cosa che finge di essere simile a una promessa, allora potrebbe essere più appropriato testare un "allora" (qualsiasi cosa con un .thenmetodo), come mostrato in altre risposte qui.


Da allora mi è stato fatto notare che Promise.resolve(obj) == objnon funzionerà in Safari. Usa instanceof Promiseinvece.
braccio

2
Questo non funziona in modo affidabile e mi ha causato un problema insanamente difficile da monitorare. Supponi di avere una libreria che utilizza lo shim es6.promise e che usi Bluebird da qualche parte, avrai problemi. Questo problema è emerso per me in Chrome Canary.
vaughan,

1
Sì, questa risposta è in realtà sbagliata. Sono finito qui per un problema così difficile da rintracciare. Dovresti obj && typeof obj.then == 'function'invece controllare , perché funzionerà con tutti i tipi di promesse ed è in realtà il modo raccomandato dalle specifiche e utilizzato dalle implementazioni / polifillamenti. I nativi, Promise.allad esempio, lavoreranno su tutte le thenabilità, non solo su altre promesse native. Così dovrebbe il tuo codice. Quindi instanceof Promisenon è una buona soluzione.
Stijn de Witt,

2
Follow-up - è peggio: Su Node.js 6.2.2 utilizzando solo promesse nativi che sto in questo momento cercando di eseguire il debug di un problema in cui console.log(typeof p, p, p instanceof Promise);produce questo risultato: object Promise { <pending> } false. Come vedi è una promessa, va bene, eppure il instanceof Promisetest ritorna false?
Mörre,

2
Ciò fallirà per le promesse che non sono dello stesso regno.
Benjamin Gruenbaum,

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

6
cosa succede se la cosa non è definita? è necessario proteggersi da ciò && ...
MrBorna

non è il migliore ma è sicuramente molto probabile; dipende anche dalla portata del problema. La scrittura al 100% difensiva è generalmente applicabile nelle API pubbliche aperte o in cui si conosce che la forma / firma dei dati è completamente aperta.
rob2d,

17

Per vedere se l'oggetto indicato è una promessa ES6 , possiamo utilizzare questo predicato:

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

Calling toStringdirettamente dai Object.prototyperitorni una rappresentazione di stringa nativa del tipo di oggetto dato che è "[object Promise]"nel nostro caso. Questo assicura che l'oggetto dato

  • Bypassa i falsi positivi come ..:
    • Tipo di oggetto auto-definito con lo stesso nome costruttore ("Promessa").
    • toStringMetodo auto-scritto dell'oggetto dato.
  • Funziona in più contesti di ambiente (ad es. Iframe) in contrasto coninstanceof o isPrototypeOf.

Tuttavia, qualsiasi oggetto host particolare , che ha il tag modificato tramiteSymbol.toStringTag , può restituire "[object Promise]". Questo potrebbe essere il risultato desiderato o meno a seconda del progetto (ad esempio se esiste un'implementazione Promise personalizzata).


Per vedere se l'oggetto proviene da una Promessa ES6 nativa , possiamo usare:

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 
}

Secondo questa e questa sezione delle specifiche, la rappresentazione in formato stringa della funzione dovrebbe essere:

" Identificatore di funzione ( FormalParameterList opt ) { FunctionBody }"

che è gestito di conseguenza sopra. Il FunctionBody è presente [native code]in tutti i principali browser.

MDN: Function.prototype.toString

Funziona anche in più contesti di ambiente.


12

Non è una risposta alla domanda completa, ma penso che valga la pena ricordare che in Node.js 10 è isPromisestata aggiunta una nuova funzione util chiamata che verifica se un oggetto è una Promessa nativa o meno:

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

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

11

Ecco come il pacchetto graphql-js rileva le promesse:

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

valueè il valore restituito della tua funzione. Sto usando questo codice nel mio progetto e finora non ho avuto problemi.


6

Ecco il modulo in codice https://github.com/ssnau/xkit/blob/master/util/is-promise.js

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

se un oggetto con un thenmetodo, dovrebbe essere trattato come a Promise.


3
perché abbiamo bisogno di obj === 'funzione' condizione tra l'altro?
Alendorff,

Come in questa risposta , qualsiasi oggetto può avere un metodo "allora" e quindi non può essere sempre trattato come una promessa.
Boghyon Hoffmann,

6

Nel caso in cui si stia utilizzando Typescript , vorrei aggiungere che è possibile utilizzare la funzione "tipo predicato". Dovrei semplicemente racchiudere la verifica logica in una funzione che ritorna x is Promise<any>e non sarà necessario eseguire i caratteri tipografici. Sotto il mio esempio, cè una promessa o uno dei miei tipi che voglio convertire in una promessa chiamando il c.fetch()metodo.

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

Maggiori informazioni: https://www.typescriptlang.org/docs/handbook/advanced-types.html


6

Se si utilizza un metodo asincrono, è possibile farlo ed evitare qualsiasi ambiguità.

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

Se la funzione restituisce promessa, attenderà e tornerà con il valore risolto. Se la funzione restituisce un valore, verrà trattata come risolta.

Se la funzione non restituisce una promessa oggi, ma domani ne restituisce una o viene dichiarata asincrona, sarai a prova di futuro.


questo funziona, secondo qui : "se il valore [atteso] non è una promessa, [l'espressione attesa] converte il valore in una Promessa risolta e la attende"
pqnet

È fondamentalmente ciò che è stato suggerito nella risposta accettata, tranne che qui viene utilizzata la sintassi 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));
});

2

Uso questa funzione come soluzione universale:

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

-1

dopo aver cercato un modo affidabile per rilevare le funzioni asincrone o anche le promesse , ho finito con il seguente test:

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

se effettui la sottoclasse Promisee ne crei istanze, questo test può fallire. questo dovrebbe funzionare per la maggior parte di ciò per cui stai provando a provare.
theram

D'accordo, ma non vedo perché qualcuno dovrebbe creare sottocategorie di promesse
Sebastien H.

fn.constructor.name === 'AsyncFunction'è sbagliato - significa che qualcosa è una funzione asincrona e non una promessa - inoltre non è garantito che funzioni perché le persone possono sottoclassare le promesse
Benjamin Gruenbaum,

@BenjaminGruenbaum L'esempio sopra funziona nella maggior parte dei casi, se crei la tua sottoclasse devi aggiungere i test sul suo nome
Sebastien H.

Puoi, ma se sai già quali oggetti ci sono già sai se le cose sono promesse o no.
Benjamin Gruenbaum,

-3

ES6:

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

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

2
Qualsiasi oggetto che ha (o ha sovrascritto) il toStringmetodo può semplicemente restituire una stringa che include "Promise".
Boghyon Hoffmann,

4
Questa risposta è negativa per molte ragioni, l'essere più ovvio'NotAPromise'.toString().includes('Promise') === true
dannatamente il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.