Wie kann ich feststellen, ob ein Objekt ein Versprechen ist?

335

Ob es sich um ein ES6-Versprechen oder ein Bluebird-Versprechen, ein Q-Versprechen usw. handelt.

Wie teste ich, ob ein bestimmtes Objekt ein Versprechen ist?

Theram
quelle
3
Bestenfalls könnten Sie nach einer .thenMethode suchen, aber das würde Ihnen nicht sagen, dass das, was Sie haben, definitiv ein Versprechen ist. Alles, was Sie an diesem Punkt wissen würden, ist, dass Sie etwas haben, das eine .thenMethode enthüllt , wie ein Versprechen.
Scott Offen
@ScottOffen Die Versprechensspezifikation macht ausdrücklich keinen Unterschied.
Benjamin Gruenbaum
6
Mein Punkt ist, dass jeder ein Objekt erstellen kann, das eine .thenMethode verfügbar macht, die kein Versprechen ist, sich nicht wie ein Versprechen verhält und nicht die Absicht hatte, wie ein Versprechen verwendet zu werden. Überprüfen auf eine .thenMethode sagt Ihnen , nur , dass das , wenn das Objekt nicht eine hat .thenMethode, dann sind Sie nicht haben ein Versprechen. Die inverse - , dass die Existenz eines .thenVerfahrens bedeutet , dass Sie tun , ein Versprechen haben - ist nicht unbedingt wahr.
Scott Offen
3
@ScottOffen Per Definition besteht die einzige Möglichkeit, ein Versprechen zu identifizieren, darin, zu überprüfen, ob es eine .thenMethode gibt. Ja, das kann zu Fehlalarmen führen, aber es wird davon ausgegangen, dass sich alle Versprechen-Bibliotheken darauf verlassen (denn darauf können sie sich nur verlassen). Die einzige Alternative, soweit ich sehen kann, besteht darin, den Vorschlag von Benjamin Gruenbaum anzunehmen und ihn durch die Versprechens-Testsuite zu führen. Für den tatsächlichen Produktionscode ist dies jedoch nicht praktikabel.
JLRishe

Antworten:

341

Wie eine Versprechensbibliothek entscheidet

Wenn es eine .thenFunktion hat, ist dies die einzige Standardversprechen-Bibliothek, die verwendet wird.

Die Promises / A + -Spezifikation hat einen Begriff namens thenable, der im Grunde "ein Objekt mit einer thenMethode" ist. Versprechen werden und sollten alles mit einer Dann-Methode aufnehmen. Alle von Ihnen erwähnten Versprechen-Implementierungen tun dies.

Wenn wir uns die Spezifikation ansehen :

2.3.3.3 Wenn thenes sich um eine Funktion handelt, rufen Sie sie mit x wie folgt auf: erstes Argument resolvePromise und zweites Argument reversePromise

Außerdem werden die Gründe für diese Entwurfsentscheidung erläutert:

Diese Behandlung von thenFähigkeiten ermöglicht die Interoperation von Versprechen-Implementierungen, solange sie eine Promises / A + -konforme thenMethode offenlegen . Es ermöglicht Promises / A + -Implementierungen auch, nicht konforme Implementierungen mit angemessenen Methoden zu „assimilieren“.

Wie solltest du dich entscheiden?

Sie sollten nicht - stattdessen nennen Promise.resolve(x)( Q(x)in Q) , die wird immer einen beliebigen Wert oder externe umwandeln thenkönnen in einer vertrauenswürdigen Versprechen. Es ist sicherer und einfacher, als diese Überprüfungen selbst durchzuführen.

Müssen Sie wirklich sicher sein?

Sie können es jederzeit über die Testsuite ausführen : D.

Benjamin Gruenbaum
quelle
95
Gute Antwort. Eine Bemerkung: "Sie sollten nicht - stattdessen Promise.resolve (x) aufrufen" ist nicht immer eine Option. Wenn Sie wirklich testen möchten, ob ein Objekt ein Versprechen ist ( dann in der Lage ist), Promise.resolvesagen Sie nichts und konvertieren dabei Objekte. Die Antwort wäre also (mit einem Objekt namens subject):var isPromise = typeof subject.then == 'function';
Stijn de Witt
11
-1. Alle Versprechen haben eine thenMethode, aber alle thenMethoden gehören nicht zu a Promise. Ihre Aussage ist daher ungültig und beantwortet daher die Frage nicht.
Linus Oleander
18
@Oleander Dies ist buchstäblich die Definition in der ECMAScript-Spezifikation und der Promises / A + -Spezifikation. Wenn es eine hat thenMethode , dann wird es als ein Versprechen behandelt und rekursiv durch assimiliert werden soll Promise.prototype.then, Promise.resolve all raceoder der fulfillParameter des Versprechen Konstruktor. Sie können also technisch anders prüfen, aber die Sprache selbst prüft häufig und sie prüft so. Ich bin mir nicht sicher, was Ihre Ablehnung bedeutet oder was daran falsch ist, da der Abschnitt, den Sie kommentieren, mit "Wie eine Versprechensbibliothek entscheidet" beginnt und das ist richtig
Benjamin Gruenbaum
5
@ Sebastian versuchen Sie das abzuwarten und Sie wären überrascht, was passiert.
Benjamin Gruenbaum
14
Dieser Thread ist frustrierend zu lesen. JavaScript ist eine Sprache vom Typ Ente. Versprechen sind in dieser Hinsicht keine Seltsamkeit. Dies ist eine ausgezeichnete Antwort.
ChrisM
168

Wenn Sie überprüfen, ob etwas versprochen wird, wird der Code unnötig kompliziert. Verwenden Sie einfach Promise.resolve

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

})
Esailija
quelle
1
Kann Promise.resolve also mit allem umgehen, was ihm in den Weg kommt? Sicher nichts, aber ich denke etwas Vernünftiges?
Alexander Mills
3
@AlexMills ja, es funktioniert sogar für nicht standardmäßige Versprechen wie jQuery-Versprechen. Es kann fehlschlagen, wenn das Objekt eine then-Methode hat, die eine völlig andere Schnittstelle als das Versprechen hat.
Esailija
19
Diese Antwort, obwohl vielleicht ein guter Rat, beantwortet die Frage nicht wirklich.
Stijn de Witt
4
Sofern es sich bei der Frage nicht wirklich um jemanden handelt, der tatsächlich eine Versprechensbibliothek implementiert, ist die Frage ungültig. Nur eine Versprechensbibliothek müsste die Prüfung durchführen. Danach können Sie immer die .resolve-Methode verwenden, wie ich gezeigt habe.
Esailija
4
@Esalija Die Frage scheint mir relevant und wichtig zu sein, nicht nur für einen Implementierer einer Versprechen-Bibliothek. Dies ist auch für einen Benutzer einer Versprechungsbibliothek relevant, der wissen möchte, wie sich Implementierungen verhalten werden / sollten / könnten und wie verschiedene Versprechungsbibliotheken miteinander interagieren. Insbesondere ist dieser Benutzer sehr bestürzt über die offensichtliche Tatsache, dass ich für jedes X ein X versprechen kann, außer wenn X "Versprechen" ist (was auch immer "Versprechen" hier bedeutet - das ist die Frage), und ich bin definitiv interessiert genau zu wissen, wo die Grenzen dieser Ausnahme liegen.
Don Hatch
103

Hier ist meine ursprüngliche Antwort, die seitdem in der Spezifikation als Test für ein Versprechen bestätigt wurde:

Promise.resolve(obj) == obj

Dies funktioniert, weil der Algorithmus explizit verlangt, dass Promise.resolvedas genaue übergebene Objekt genau dann zurückgegeben werden muss, wenn es nach der Definition der Spezifikation ein Versprechen ist.

Ich habe hier eine andere Antwort, die dies früher sagte, aber ich habe sie in etwas anderes geändert, als es zu diesem Zeitpunkt mit Safari nicht funktionierte. Das war vor einem Jahr und das funktioniert jetzt auch in Safari zuverlässig.

Ich hätte meine ursprüngliche Antwort bearbeitet, außer das fühlte sich falsch an, da inzwischen mehr Menschen für die geänderte Lösung in dieser Antwort gestimmt haben als für das Original. Ich glaube, das ist die bessere Antwort, und ich hoffe, Sie stimmen zu.

Ausleger
quelle
10
sollten Sie ===anstelle von verwenden ==?
Neil S
12
Dies wird auch für Versprechen scheitern, die nicht aus dem gleichen Bereich stammen.
Benjamin Gruenbaum
4
"ein Versprechen nach der Definition der Spezifikation" scheint gemein zu sein "ein Versprechen, das vom selben Konstruktor erstellt wurde wie ein Versprechen, das durch Promise.resolve () erstellt wurde" - dies wird also nicht erkennen, ob z. Ein polygefülltes Versprechen ist eigentlich ein Versprechen
VoxPelli
3
Diese Antwort könnte verbessert werden, wenn zunächst angegeben wird, wie Sie die Frage interpretieren, anstatt sofort mit einer Antwort zu beginnen. Das OP hat dies leider überhaupt nicht klargestellt, und Sie haben dies auch noch nicht getan Das OP, der Verfasser und der Leser befinden sich wahrscheinlich auf drei verschiedenen Seiten. In dem Dokument, auf das Sie sich beziehen, heißt es: "Wenn das Argument ein von diesem Konstruktor erstelltes Versprechen ist ", wobei der kursiv geschriebene Teil von entscheidender Bedeutung ist. Es wäre gut zu sagen, dass dies die Frage ist, die Sie beantworten. Auch, dass Ihre Antwort für einen Benutzer dieser Bibliothek nützlich ist, aber nicht für den Implementierer.
Don Hatch
1
Verwenden Sie diese Methode nicht, hier ist der Grund, mehr zu @ BenjaminGruenbaums Punkt. gist.github.com/reggi/a1da4d0ea4f1320fa15405fb86358cff
ThomasReggi
61

Update: Dies ist nicht mehr die beste Antwort. Bitte stimmen Sie stattdessen meine andere Antwort ab .

obj instanceof Promise

Sollte es tun. Beachten Sie, dass dies möglicherweise nur mit nativen es6-Versprechungen zuverlässig funktioniert.

Wenn Sie eine Unterlegscheibe, eine Versprechensbibliothek oder etwas anderes verwenden, das vorgibt, wie ein Versprechen zu sein, ist es möglicherweise besser, auf ein "thenable" (alles mit einer .thenMethode) zu testen , wie in anderen Antworten hier gezeigt.

Ausleger
quelle
Es hat sich seit mir darauf hingewiesen worden , dass Promise.resolve(obj) == objnicht funktionieren in Safari. Verwenden Sie instanceof Promisestattdessen.
Fock
2
Dies funktioniert nicht zuverlässig und hat mir ein wahnsinnig schwer zu verfolgendes Problem verursacht. Angenommen, Sie haben eine Bibliothek, die den Shim es6.promise verwendet, und Sie verwenden Bluebird irgendwo. Sie werden Probleme haben. Dieses Problem trat bei Chrome Canary auf.
Vaughan
1
Ja, diese Antwort ist tatsächlich falsch. Ich bin wegen genau einem so schwer zu verfolgenden Problem hier gelandet. Sie sollten dies obj && typeof obj.then == 'function'stattdessen wirklich überprüfen , da es mit allen Arten von Versprechungen funktioniert und tatsächlich die von der Spezifikation empfohlene und von den Implementierungen / Polyfills verwendete Methode ist. Einheimische Promise.allzum Beispiel funktionieren bei allen thenFähigkeiten, nicht nur bei anderen einheimischen Versprechungen. So sollte Ihr Code sein. Ist instanceof Promisealso keine gute Lösung.
Stijn de Witt
2
Follow-up - es ist schlimmer: Auf node.js 6.2.2, das nur native Versprechen verwendet, versuche ich gerade, ein Problem zu debuggen, bei dem console.log(typeof p, p, p instanceof Promise);diese Ausgabe erzeugt wird : object Promise { <pending> } false. Wie Sie sehen, ist es ein Versprechen in Ordnung - und doch instanceof Promisekehrt der Test zurück false?
Mörre
2
Dies wird für Versprechen scheitern, die nicht aus dem gleichen Bereich stammen.
Benjamin Gruenbaum
46
if (typeof thing.then === 'function') {
    // probably a promise
} else {
    // definitely not a promise
}
unobf
quelle
6
Was ist, wenn etwas undefiniert ist? Sie müssen sich dagegen schützen, indem Sie && ...
mrBorna
nicht am besten, aber definitiv sehr wahrscheinlich; hängt auch vom Umfang des Problems ab. 100% defensives Schreiben ist normalerweise in öffentlichen APIs mit offenem Ende anwendbar oder wenn Sie wissen, dass die Form / Signatur von Daten vollständig offen ist.
rob2d
17

Um zu sehen, ob das angegebene Objekt ein ES6-Versprechen ist , können wir dieses Prädikat verwenden:

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

Calling toStringdirekt von den Object.prototypeRenditen einer nativen Stringdarstellung des Objekttypen gegeben , das ist "[object Promise]"in unserem Fall. Dies stellt sicher, dass das gegebene Objekt

  • Umgeht falsch positive Ergebnisse wie ..:
    • Selbstdefinierter Objekttyp mit demselben Konstruktornamen ("Promise").
    • Selbstgeschriebene toStringMethode des gegebenen Objekts.
  • Funktioniert in mehreren Umgebungskontexten (z. B. Iframes) im Gegensatz zuinstanceof oder isPrototypeOf.

Jedes bestimmte Hostobjekt , dessen Tag über geändert wurdeSymbol.toStringTag , kann jedoch zurückkehren "[object Promise]". Dies kann je nach Projekt das beabsichtigte Ergebnis sein oder auch nicht (z. B. wenn eine benutzerdefinierte Promise-Implementierung vorhanden ist).


Um festzustellen, ob das Objekt aus einem nativen ES6-Versprechen stammt , können wir Folgendes verwenden:

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 
}

Gemäß diesem und diesem Abschnitt der Spezifikation sollte die Zeichenfolgendarstellung der Funktion sein:

"Funktion Identifier ( Formale opt ) { FunctionBody }"

was oben entsprechend behandelt wird. Der FunctionBody befindet sich [native code]in allen gängigen Browsern.

MDN: Function.prototype.toString

Dies funktioniert auch in mehreren Umgebungskontexten.

Boghyon Hoffmann
quelle
11

Keine Antwort auf die vollständige Frage, aber ich denke, es ist erwähnenswert, dass in Node.js 10 eine neue Util-Funktion namens aufgerufen isPromisewurde, die prüft, ob ein Objekt ein natives Versprechen ist oder nicht:

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

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

So erkennt das Paket graphql-js Versprechen:

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

valueist der zurückgegebene Wert Ihrer Funktion. Ich verwende diesen Code in meinem Projekt und habe bisher kein Problem.

Muratgozel
quelle
6

Hier ist das Codeformular https://github.com/ssnau/xkit/blob/master/util/is-promise.js

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

Wenn ein Objekt mit einer thenMethode, sollte es als behandelt werden Promise.

ssnau
quelle
3
warum brauchen wir übrigens obj === 'function' Bedingung?
Alendorff
Wie bei dieser Antwort kann jedes Objekt eine Methode "dann" haben und daher nicht immer als Versprechen behandelt werden.
Boghyon Hoffmann
6

Wenn Sie Typescript verwenden , möchte ich hinzufügen, dass Sie die Funktion "Typprädikat" verwenden können. Sie sollten nur die logische Überprüfung in eine Funktion einschließen, die zurückgibt, x is Promise<any>und Sie müssen keine Typecasts durchführen. Unten in meinem Beispiel cist entweder ein Versprechen oder einer meiner Typen, die ich durch Aufrufen der c.fetch()Methode in ein Versprechen umwandeln möchte .

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

Weitere Informationen: https://www.typescriptlang.org/docs/handbook/advanced-types.html

Murilo Perrone
quelle
6

Wenn Sie sich in einer asynchronen Methode befinden, können Sie dies tun und Unklarheiten vermeiden.

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

Wenn die Funktion Versprechen zurückgibt, wartet sie mit dem aufgelösten Wert und kehrt zurück. Wenn die Funktion einen Wert zurückgibt, wird dieser als aufgelöst behandelt.

Wenn die Funktion heute kein Versprechen zurückgibt, aber morgen eines zurückgibt oder als asynchron deklariert wird, sind Sie zukunftssicher.

Steven Spungin
quelle
Dies funktioniert laut hier : "Wenn der [erwartete] Wert kein Versprechen ist, wandelt [der erwartete Ausdruck] den Wert in ein aufgelöstes Versprechen um und wartet darauf"
pqnet
Es ist im Grunde das, was in der akzeptierten Antwort vorgeschlagen wurde, außer dass hier die Promise.resolve()
asynchrone Warte
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));
});
Rotkohl
quelle
2

Ich benutze diese Funktion als universelle Lösung:

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

Nachdem ich nach einer zuverlässigen Methode gesucht hatte , um Async- Funktionen oder sogar Versprechen zu erkennen , verwendete ich den folgenden Test:

() => fn.constructor.name === 'Promise' || fn.constructor.name === 'AsyncFunction'
Sebastien H.
quelle
Wenn Sie PromiseInstanzen davon unterordnen und erstellen, kann dieser Test fehlschlagen. Dies sollte jedoch für die meisten Dinge funktionieren, auf die Sie testen möchten.
Theram
Einverstanden, aber ich verstehe nicht, warum jemand Untervermietungen von Versprechungen schaffen sollte
Sebastien H.
fn.constructor.name === 'AsyncFunction'ist falsch - es bedeutet, dass etwas eine asynchrone Funktion und kein Versprechen ist - es ist auch nicht garantiert, dass es funktioniert, weil Leute Versprechen
unterordnen
@BenjaminGruenbaum Das obige Beispiel funktioniert in den meisten Fällen. Wenn Sie Ihre eigene Unterklasse erstellen, sollten Sie die Tests zu ihrem Namen hinzufügen
Sebastien H.
Sie können, aber wenn Sie bereits wissen, welche Objekte es gibt, wissen Sie bereits, ob Dinge Versprechen sind oder nicht.
Benjamin Gruenbaum
-3

ES6:

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

console.log(promise.toString().includes('Promise')); //true
Mathias Gheno Azzolini
quelle
2
Jedes Objekt, das eine toStringMethode hat (oder überschrieben hat) , kann einfach eine Zeichenfolge zurückgeben, die enthält "Promise".
Boghyon Hoffmann
4
Diese Antwort ist aus vielen Gründen schlecht, das offensichtlichste ist'NotAPromise'.toString().includes('Promise') === true
verdammt