Comment savoir si un objet est une promesse?

336

Qu'il s'agisse d'une promesse ES6 ou d'une promesse bluebird, d'une promesse Q, etc.

Comment puis-je tester pour voir si un objet donné est une promesse?

le bélier
la source
3
Au mieux, vous pouvez vérifier une .thenméthode, mais cela ne vous dirait pas que ce que vous avez est définitivement une promesse. Tout ce que vous savez à ce stade, c'est que vous avez quelque chose qui expose une .thenméthode, comme une promesse.
Scott Offen
@ScottOffen la spécification de promesse ne fait explicitement aucune distinction.
Benjamin Gruenbaum
6
Mon point est que n'importe qui peut créer un objet qui expose une .thenméthode qui n'est pas une promesse, ne se comporte pas comme une promesse et n'avait aucune intention d'être utilisée comme une promesse. Vérification d'une .thenméthode vous indique juste que le si l' objet n'a pas avoir une .thenméthode, vous n'avez une promesse. L'inverse - que l'existence d'un moyen de la méthode que vous faites ont une promesse - n'est pas nécessairement vrai. .then
Scott Offen
3
@ScottOffen Par définition, la seule façon établie d'identifier une promesse est de vérifier si elle a une .thenméthode. Oui, cela a le potentiel de faux positifs, mais c'est l'hypothèse sur laquelle toutes les bibliothèques de promesses comptent (car c'est tout ce sur quoi elles peuvent compter). La seule alternative pour autant que je puisse voir est de prendre la suggestion de Benjamin Gruenbaum et de l'exécuter dans la suite de tests promesse. Mais ce n'est pas pratique pour le code de production réel.
JLRishe

Réponses:

342

Comment une bibliothèque de promesses décide

S'il a une .thenfonction - c'est la seule promesse standard que les bibliothèques utilisent.

La spécification Promises / A + a une notion appelée thencapable qui est fondamentalement "un objet avec une thenméthode". Les promesses assimileront et devraient assimiler n'importe quoi avec une méthode alors. Toutes les mises en œuvre de promesses que vous avez mentionnées le font.

Si nous regardons la spécification :

2.3.3.3 si thenest une fonction, appelez-la avec x comme ceci, premier argument resolverPromise et deuxième argument rejettePromise

Il explique également la justification de cette décision de conception:

Ce traitement des thencapacités permet aux implémentations de promesses d'interopérer, tant qu'elles exposent une thenméthode conforme à Promises / A + . Il permet également aux implémentations Promises / A + d '«assimiler» les implémentations non conformes avec des méthodes raisonnables.

Comment vous devez décider

Vous ne devriez pas - à la place appeler Promise.resolve(x)( Q(x)en Q) qui convertira toujours n'importe quelle valeur ou externe thenen une promesse de confiance. C'est plus sûr et plus facile que d'effectuer ces vérifications vous-même.

vraiment besoin d'être sûr?

Vous pouvez toujours l'exécuter via la suite de tests : D

Benjamin Gruenbaum
la source
168

Vérifier si quelque chose est promis complique inutilement le code, utilisez simplement Promise.resolve

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

})
Esailija
la source
1
donc Promise.resolve peut gérer tout ce qui se présente à vous? Sûrement rien, mais je suppose que quelque chose de raisonnable?
Alexander Mills
3
@AlexMills oui, cela fonctionne même pour les promesses non standard comme la promesse jQuery. Il peut échouer si l'objet a une méthode then qui a une interface complètement différente de promise.
Esailija
19
Cette réponse, bien que peut-être un bon conseil, ne répond pas réellement à la question.
Stijn de Witt
4
À moins que la question ne concerne vraiment quelqu'un qui implémente réellement une bibliothèque de promesses, la question n'est pas valide. Seule une bibliothèque de promesses aurait besoin de faire la vérification, après quoi vous pouvez toujours utiliser sa méthode .resolve comme je l'ai montré.
Esailija
4
@Esalija La question me semble pertinente et importante, pas seulement pour un réalisateur d'une bibliothèque de promesses. Il est également pertinent pour un utilisateur d'une bibliothèque de promesses qui souhaite savoir comment les implémentations se comporteront / devraient / pourraient se comporter et comment les différentes bibliothèques de promesses interagiraient entre elles. En particulier, cet utilisateur est très consterné par le fait apparent que je peux faire une promesse d'un X pour n'importe quel X, sauf lorsque X est "promesse" (quelle que soit la "promesse" signifie ici - c'est la question), et je suis vraiment intéressé à savoir exactement où se trouvent les limites de cette exception.
Don Hatch
104

Voici ma réponse originale, qui a depuis été ratifiée dans la spécification comme moyen de tester une promesse:

Promise.resolve(obj) == obj

Cela fonctionne parce que l' algorithme demande explicitement que Promise.resolvedoit renvoyer l'objet exact transmis si et seulement si c'est une promesse par la définition de la spécification.

J'ai une autre réponse ici, qui disait cela, mais je l'ai changé pour autre chose quand cela ne fonctionnait pas avec Safari à l'époque. C'était il y a un an, et cela fonctionne désormais de manière fiable même dans Safari.

J'aurais modifié ma réponse d'origine, sauf que cela ne me semblait pas correct, étant donné que plus de personnes ont maintenant voté pour la solution modifiée dans cette réponse que l'original. Je pense que c'est la meilleure réponse, et j'espère que vous êtes d'accord.

foc
la source
10
devez-vous utiliser à la ===place de ==?
Neil S
12
Cela échouera également pour les promesses qui ne sont pas du même ordre.
Benjamin Gruenbaum
4
"une promesse par la définition de la spécification" semble signifier "une promesse créée par le même constructeur qu'une promesse créée via Promise.resolve () serait" - donc cela ne parviendra pas à détecter si par exemple. une promesse polyfilled est en fait une promesse
VoxPelli
3
Cette réponse pourrait être améliorée si elle commençait par indiquer comment vous interprétez la question plutôt que de commencer par une réponse tout de suite - le PO n'a malheureusement pas été clair du tout, et vous ne l'avez pas non plus, donc à ce stade l'OP, l'écrivain et le lecteur sont probablement sur 3 pages différentes. Le document auquel vous vous référez dit "si l'argument est une promesse produite par ce constructeur ", la partie en italique étant cruciale. Il serait bon de dire que c'est la question à laquelle vous répondez. Aussi que votre réponse est utile pour un utilisateur de cette bibliothèque mais pas pour l'implémenteur.
Don Hatch
1
N'utilisez pas cette méthode, voici pourquoi, plus au point de @ BenjaminGruenbaum. gist.github.com/reggi/a1da4d0ea4f1320fa15405fb86358cff
ThomasReggi
61

Mise à jour: ce n'est plus la meilleure réponse. Veuillez voter mon autre réponse à la place.

obj instanceof Promise

devrait le faire. Notez que cela ne peut fonctionner de manière fiable qu'avec les promesses natives d'es6.

Si vous utilisez une cale, une bibliothèque de promesses ou tout autre élément faisant semblant de ressembler à une promesse, il peut être plus approprié de tester un élément "utilisable" (tout ce qui a une .thenméthode), comme indiqué dans d'autres réponses ici.

foc
la source
Il m'a depuis été signalé que cela Promise.resolve(obj) == objne fonctionnera pas dans Safari. Utilisez instanceof Promiseplutôt.
foc
2
Cela ne fonctionne pas de manière fiable et m'a causé un problème incroyablement difficile à suivre. Supposons que vous ayez une bibliothèque qui utilise le shim es6.promise et que vous utilisez Bluebird quelque part, vous aurez des problèmes. Ce problème est apparu pour moi dans Chrome Canary.
vaughan
1
Oui, cette réponse est en fait fausse. Je me suis retrouvé ici pour un problème aussi difficile à suivre. Vous devriez vraiment vérifier à la obj && typeof obj.then == 'function'place, car cela fonctionnera avec tous les types de promesses et est en fait la manière recommandée par la spécification et utilisée par les implémentations / polyfills. Native Promise.allpar exemple fonctionnera sur toutes les thencapacités, pas seulement sur d'autres promesses natives. Il en va de même pour votre code. Ce instanceof Promisen'est donc pas une bonne solution.
Stijn de Witt
2
Followup - c'est pire: Sur node.js 6.2.2 en utilisant uniquement des promesses indigènes Je suis juste en essayant maintenant de déboguer un problème où console.log(typeof p, p, p instanceof Promise);produit cette sortie: object Promise { <pending> } false. Comme vous pouvez le voir, c'est une promesse, et pourtant le instanceof Promisetest revient false?
Mörre
2
Cela échouera pour des promesses qui ne sont pas du même ordre.
Benjamin Gruenbaum
46
if (typeof thing.then === 'function') {
    // probably a promise
} else {
    // definitely not a promise
}
unobf
la source
6
et si la chose n'est pas définie? vous devez vous prémunir contre cela via chose && ...
mrBorna
pas le meilleur mais est certainement très probable; dépend également de l'étendue du problème. L'écriture défensive à 100% est généralement applicable dans les API publiques ouvertes ou lorsque vous savez que la forme / signature des données est complètement ouverte.
rob2d
17

Pour voir si l'objet donné est une promesse ES6 , nous pouvons utiliser ce prédicat:

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

Calling toStringdirectement à partir du Object.prototyperetourne une représentation sous forme de chaîne native du type d'objet donné, ce qui est "[object Promise]"dans notre cas. Cela garantit que l'objet donné

  • Contourne les faux positifs tels que ..:
    • Type d'objet auto-défini avec le même nom de constructeur ("Promise").
    • toStringMéthode auto-écrite de l'objet donné.
  • Fonctionne dans plusieurs contextes d'environnement (par exemple iframes) contrairement àinstanceof ou isPrototypeOf.

Cependant, tout objet hôte particulier , dont la balise est modifiée viaSymbol.toStringTag , peut retourner "[object Promise]". Cela peut être le résultat souhaité ou non selon le projet (par exemple, s'il existe une implémentation Promise personnalisée).


Pour voir si l'objet provient d'une promesse ES6 native , nous pouvons utiliser:

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 
}

Selon ceci et cette section de la spécification, la représentation sous forme de chaîne de la fonction devrait être:

" identificateur de fonction ( FormalParameterList opt ) { FunctionBody }"

qui est traité en conséquence ci-dessus. Le FunctionBody est [native code]dans tous les principaux navigateurs.

MDN: Function.prototype.toString

Cela fonctionne également dans plusieurs contextes d'environnement.

Boghyon Hoffmann
la source
12

Pas une réponse à la question complète, mais je pense qu'il vaut la peine de mentionner que dans Node.js 10, une nouvelle fonction util appelée a isPromiseété ajoutée qui vérifie si un objet est une promesse native ou non:

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

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

Voici comment le package graphql-js détecte les promesses:

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

valueest la valeur retournée de votre fonction. J'utilise ce code dans mon projet et je n'ai aucun problème jusqu'à présent.

muratgozel
la source
6

Voici la forme de code https://github.com/ssnau/xkit/blob/master/util/is-promise.js

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

si un objet avec une thenméthode, il doit être traité comme un Promise.

ssnau
la source
3
pourquoi nous avons besoin obj === 'fonction' condition btw?
Alendorff
De même que cette réponse , tout objet peut avoir une méthode "alors" et ne peut donc pas toujours être traité comme une promesse.
Boghyon Hoffmann
6

Dans le cas où vous utilisez Typescript , je voudrais ajouter que vous pouvez utiliser la fonction "type prédicat". Il suffit d'envelopper la vérification logique dans une fonction qui revient x is Promise<any>et vous n'aurez pas besoin de faire de typecasts. Ci-dessous sur mon exemple, cest soit une promesse ou l'un de mes types que je veux convertir en promesse en appelant la c.fetch()méthode.

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

Plus d'informations: https://www.typescriptlang.org/docs/handbook/advanced-types.html

Murilo Perrone
la source
6

Si vous utilisez une méthode asynchrone, vous pouvez le faire et éviter toute ambiguïté.

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

Si la fonction renvoie promesse, elle attendra et reviendra avec la valeur résolue. Si la fonction renvoie une valeur, elle sera traitée comme résolue.

Si la fonction ne renvoie pas de promesse aujourd'hui, mais que demain en retourne une ou est déclarée asynchrone, vous serez à l'épreuve du temps.

Steven Spungin
la source
cela fonctionne, selon ici : "si la valeur [attendue] n'est pas une promesse, [l'expression d'attente] convertit la valeur en promesse résolue, et l'attend"
pqnet
C'est fondamentalement ce qui a été suggéré dans la réponse acceptée, sauf qu'ici la syntaxe asynchrone est utilisée à la place dePromise.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));
});
chou rouge
la source
2

J'utilise cette fonction comme une solution universelle:

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

après avoir cherché un moyen fiable de détecter les fonctions asynchrones ou même les promesses , j'ai fini par utiliser le test suivant:

() => fn.constructor.name === 'Promise' || fn.constructor.name === 'AsyncFunction'
Sebastien H.
la source
si vous sous Promise- classe et créez des instances de cela, ce test peut échouer. cela devrait cependant fonctionner pour la plupart de ce que vous essayez de tester.
theram
D'accord, mais je ne vois pas pourquoi quelqu'un créerait des sous-classes de promesses
Sebastien H.
fn.constructor.name === 'AsyncFunction'est faux - cela signifie que quelque chose est une fonction asynchrone et non une promesse - il n'est pas non plus garanti de fonctionner parce que les gens peuvent sous
classer les
@BenjaminGruenbaum L'exemple ci-dessus fonctionne dans la plupart des cas, si vous créez votre propre sous-classe, vous devez ajouter les tests sur son nom
Sebastien H.
Vous pouvez, mais si vous savez déjà quels objets il y a, vous savez déjà si les choses sont des promesses ou non.
Benjamin Gruenbaum
-3

ES6:

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

console.log(promise.toString().includes('Promise')); //true
Mathias Gheno Azzolini
la source
2
Tout objet qui a (ou a écrasé) une toStringméthode peut simplement renvoyer une chaîne qui inclut "Promise".
Boghyon Hoffmann
4
Cette réponse est mauvaise pour de nombreuses raisons, la plus évidente étant'NotAPromise'.toString().includes('Promise') === true
putain de