Jak sprawdzić, czy obiekt jest obietnicą?

336

Niezależnie od tego, czy jest to obietnica ES6, czy obietnica bluebird, obietnica Q itp.

Jak sprawdzić, czy dany obiekt jest obietnicą?

theram
źródło
3
W najlepszym razie możesz sprawdzić .thenmetodę, ale to nie powiedziałoby ci, że to, co masz, jest zdecydowanie obietnicą. W tym momencie wiedziałbyś tylko, że masz coś, co obnaża .thenmetodę, na przykład obietnicę.
Scott Offen,
@ ScottOffen specyfikacja obietnicy wyraźnie nie czyni rozróżnienia.
Benjamin Gruenbaum
6
Chodzi mi o to, że każdy może stworzyć obiekt, który ujawnia .thenmetodę, która nie jest Obietnicą, nie zachowuje się jak Obietnica i nie miał zamiaru być używany jak Obietnica. Sprawdzanie .thenmetody informuje tylko, że jeśli obiekt nie ma .thenmetody, to nie masz obietnicy. Odwrotna - że istnienie .thenmetody oznacza, że zrobić mają obietnicę - niekoniecznie jest prawdą.
Scott Offen
3
@ScottOffen Z definicji jedynym ustalonym sposobem identyfikacji obietnicy jest sprawdzenie, czy ma ona jakąś .thenmetodę. Tak, ma to potencjał fałszywych trafień, ale jest to założenie, na którym polegają wszystkie biblioteki obiecujące (ponieważ tylko na tym mogą polegać). Jedyną alternatywą, jaką widzę, jest skorzystanie z sugestii Benjamina Gruenbauma i przejrzenie jej przez pakiet testów obietnic. Ale to nie jest praktyczne w przypadku rzeczywistego kodu produkcyjnego.
JLRishe,

Odpowiedzi:

342

Jak decyduje biblioteka obietnic

Jeśli ma jakąś .thenfunkcję - jest to jedyna standardowa używana biblioteka bibliotek obietnic.

Specyfikacja Promises / A + ma pojęcie „ thenzdolny”, które jest w zasadzie „przedmiotem z thenmetodą”. Obietnice będą i powinny przyswoić sobie wszystko za pomocą ówczesnej metody. Wszystkie wspomniane obietnice wykonania to robią.

Jeśli spojrzymy na specyfikację :

2.3.3.3 Jeśli thenjest funkcją, wywołaj ją z x jako tym, pierwszym argumentem resolPromise i drugim argumentem odrzuceniemPromise

Wyjaśnia również uzasadnienie tej decyzji projektowej:

Takie traktowanie umiejętności thenumożliwia współdziałanie implementacji obietnic, o ile ujawniają one thenmetodę zgodną z obietnicami / A + . Umożliwia także implementacjom Promises / A + „asymilację” niezgodnych implementacji za pomocą rozsądnych metod.

Jak powinieneś zdecydować

Nie powinieneś - zamiast tego zadzwoń Promise.resolve(x)( Q(x)w Q), który zawsze przekształci dowolną wartość lub zewnętrzną thenzdolność w zaufaną obietnicę. Jest to bezpieczniejsze i łatwiejsze niż samodzielne przeprowadzanie tych kontroli.

naprawdę musisz być pewien?

Zawsze możesz uruchomić go za pomocą zestawu testów : D

Benjamin Gruenbaum
źródło
168

Sprawdzanie, czy coś jest obiecane, niepotrzebnie komplikuje kod, wystarczy użyć Promise.resolve

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

})
Esailija
źródło
1
więc Promise.resolve poradzi sobie ze wszystkim , co stanie na jej drodze? Na pewno nic, ale chyba coś rozsądnego?
Alexander Mills,
3
@AlexMills tak, działa nawet w przypadku niestandardowych obietnic, takich jak obietnica jQuery. Może się nie powieść, jeśli obiekt ma metodę wtedy, która ma zupełnie inny interfejs niż wówczas obietnica.
Esailija,
19
Ta odpowiedź, choć może dobra rada, w rzeczywistości nie odpowiada na pytanie.
Stijn de Witt
4
O ile pytanie tak naprawdę nie dotyczy kogoś, kto faktycznie wdraża bibliotekę obietnic, pytanie jest nieprawidłowe. Tylko biblioteka obietnic będzie musiała wykonać sprawdzenie, po czym zawsze możesz użyć metody .resolve, jak pokazałem.
Esailija,
4
@Esalija Pytanie to wydaje mi się istotne i ważne, nie tylko dla realizatora biblioteki obietnic. Jest to również istotne dla użytkownika biblioteki obietnic, który chce wiedzieć, jak implementacje będą / powinny / mogły się zachowywać i jak różne biblioteki obietnic będą ze sobą współdziałać. W szczególności ten użytkownik jest bardzo przerażony faktem, że mogę złożyć obietnicę X dla dowolnego X, z wyjątkiem sytuacji, gdy X jest „obietnicą” (cokolwiek „obietnica” oznacza tutaj - to jest pytanie), i jestem zdecydowanie zainteresowany wiedząc dokładnie, gdzie leżą granice tego wyjątku.
Don Hatch
103

Oto moja pierwotna odpowiedź, która została ratyfikowana w specyfikacji jako sposób na sprawdzenie obietnicy:

Promise.resolve(obj) == obj

Działa to, ponieważ algorytm wyraźnie wymaga, Promise.resolveaby zwracał dokładnie przekazany obiekt wtedy i tylko wtedy, gdy jest to obietnica z definicji specyfikacji.

Mam tutaj inną odpowiedź, która kiedyś to mówiła, ale zmieniłem ją na coś innego, gdy w tym czasie nie działała z Safari. To było rok temu, a teraz działa niezawodnie nawet w Safari.

Zredagowałbym moją pierwotną odpowiedź, z wyjątkiem tego, że czułem się źle, biorąc pod uwagę, że więcej osób głosowało teraz na zmienione rozwiązanie w tej odpowiedzi niż w oryginale. Wierzę, że to lepsza odpowiedź i mam nadzieję, że się zgadzasz.

wysięgnik
źródło
10
powinieneś użyć ===zamiast ==?
Neil S
12
To się nie powiedzie również w przypadku obietnic, które nie są z tej samej dziedziny.
Benjamin Gruenbaum,
4
„obietnica z definicji specyfikacji” wydaje się oznaczać „obietnica stworzona przez tego samego konstruktora, co obietnica utworzona za pomocą Promise.resolve ()” - więc to nie wykryje, czy np. wypełniona obietnica jest w rzeczywistości obietnicą
VoxPelli,
3
Ta odpowiedź mogłaby zostać ulepszona, gdyby zaczynała się od podania, w jaki sposób interpretujesz pytanie, a nie od razu od odpowiedzi - OP niestety nie wyjaśnił wcale, a ty też nie, więc w tym momencie OP, pisarz i czytelnik są prawdopodobnie na 3 różnych stronach. Dokument, do którego się odwołujesz, mówi „jeśli argument jest obietnicą stworzoną przez tego konstruktora ”, przy czym część kursywą jest kluczowa. Dobrze byłoby powiedzieć, że na to pytanie odpowiadasz. Również twoja odpowiedź jest przydatna dla użytkownika tej biblioteki, ale nie dla implementatora.
Don Hatch,
1
Nie używaj tej metody, oto dlaczego, bardziej na temat @ BenjaminGruenbaum. gist.github.com/reggi/a1da4d0ea4f1320fa15405fb86358cff
ThomasReggi
61

Aktualizacja: To nie jest już najlepsza odpowiedź. Zamiast tego proszę głosować na moją drugą odpowiedź .

obj instanceof Promise

powinien to zrobić. Pamiętaj, że może to działać niezawodnie tylko z natywnymi obietnicami es6.

Jeśli używasz podkładki dystansowej, biblioteki obietnic lub czegokolwiek innego, co udaje, że jest podobna do obietnicy, bardziej odpowiednie może być przetestowanie „niemożliwego” (cokolwiek za pomocą .thenmetody), jak pokazano w innych odpowiedziach tutaj.

wysięgnik
źródło
Od tego czasu wskazano mi, że Promise.resolve(obj) == objnie będzie działać w Safari. Użyj instanceof Promisezamiast tego.
wysięg
2
To nie działa niezawodnie i spowodowało niesamowicie trudny do śledzenia problem. Załóżmy, że masz bibliotekę korzystającą z podkładki es6.promise i gdzieś używasz Bluebird, będziesz mieć problemy. Ten problem pojawił się w Chrome Canary.
vaughan
1
Tak, ta odpowiedź jest błędna. Skończyłem tutaj z powodu tak trudnego do wyśledzenia problemu. Naprawdę powinieneś sprawdzić obj && typeof obj.then == 'function', ponieważ będzie działał ze wszystkimi typami obietnic i jest w rzeczywistości sposobem zalecanym przez specyfikację i używanym przez implementacje / polifill. Natywny Promise.allna przykład działa na wszystkich thenumiejętnościach, nie tylko na innych rodzimych obietnicach. Twój kod też powinien. To instanceof Promisenie jest dobre rozwiązanie.
Stijn de Witt
2
Nawiązanie - to gorzej: Na node.js 6.2.2 używając tylko rodzimych obietnic Mam teraz próbują debugować problem gdzie console.log(typeof p, p, p instanceof Promise);produkuje ten wyjściowe: object Promise { <pending> } false. Jak widać, jest to obietnica w porządku - a jednak instanceof Promisetest powraca false?
Mörre
2
To się nie powiedzie w przypadku obietnic, które nie są z tej samej dziedziny.
Benjamin Gruenbaum,
46
if (typeof thing.then === 'function') {
    // probably a promise
} else {
    // definitely not a promise
}
unobf
źródło
6
co jeśli coś jest niezdefiniowane? musisz się przed tym bronić za pośrednictwem rzeczy i& ...
mrBorna,
nie najlepiej, ale jest zdecydowanie bardzo prawdopodobne; zależy również od zakresu problemu. Pisanie w 100% obronnie ma zwykle zastosowanie w otwartych publicznych interfejsach API lub tam, gdzie wiesz, że kształt / podpis danych jest całkowicie otwarty.
rob2d
17

Aby sprawdzić, czy dany obiekt jest obietnicą ES6 , możemy skorzystać z tego predykatu:

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

Calling toStringbezpośrednio z Object.prototypezwraca natywną reprezentację ciągu danego typu obiektu, co "[object Promise]"w naszym przypadku. Zapewnia to, że dany obiekt

  • Pomija fałszywe alarmy, takie jak:
    • Zdefiniowany typ obiektu o tej samej nazwie konstruktora („Obietnica”).
    • Self-napisany toStringmetodą danego obiektu.
  • Działa w wielu kontekstach środowiska (np. Iframe) w przeciwieństwie doinstanceof lub isPrototypeOf.

Jednak każdy konkretny obiekt hosta , który ma zmodyfikowany znacznikSymbol.toStringTag , może zwrócić "[object Promise]". Może to być zamierzony wynik lub nie, zależnie od projektu (np. Jeśli istnieje niestandardowa implementacja Promise).


Aby sprawdzić, czy obiekt pochodzi z natywnej obietnicy ES6 , możemy użyć:

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 
}

Zgodnie z i niniejszą sekcją specyfikacji ciąg reprezentujący funkcję powinien być:

„function Identifier ( FormalParameterList opt ) { FunctionBody }”

co jest odpowiednio obsługiwane powyżej. FunctionBody jest [native code]we wszystkich głównych przeglądarkach.

MDN: Function.prototype.toString

Działa to również w wielu kontekstach środowiska.

Boghyon Hoffmann
źródło
12

Nie jest to odpowiedź na pełne pytanie, ale myślę, że warto wspomnieć, że w Node.js 10 isPromisezostała dodana nowa funkcja wywołana, która sprawdza, czy obiekt jest rodzimą obietnicą, czy nie:

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

utilTypes.isPromise(Promise.resolve(5)) // true
utilTypes.isPromise(b_Promise.resolve(5)) // false
LEQADA
źródło
11

Oto jak pakiet graphql-js wykrywa obietnice:

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

valuejest zwróconą wartością twojej funkcji. Używam tego kodu w moim projekcie i jak dotąd nie mam z tym problemu.

muratgozel
źródło
6

Oto formularz kodu https://github.com/ssnau/xkit/blob/master/util/is-promise.js

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

jeśli obiekt z thenmetodą, powinien być traktowany jako Promise.

ssnau
źródło
3
dlaczego potrzebujemy obj === „funkcja” warunek btw?
Alendorff,
Tak jak w przypadku tej odpowiedzi , każdy obiekt może mieć metodę „wtedy” i dlatego nie zawsze może być traktowany jako obietnica.
Boghyon Hoffmann
6

Jeśli używasz Typescript , chciałbym dodać, że możesz użyć funkcji „predykat typu”. Powinien po prostu zawrzeć logiczną weryfikację w funkcji, która powraca, x is Promise<any>i nie trzeba wykonywać typecastów. Poniżej na moim przykładzie cznajduje się albo obietnica, albo jeden z moich typów, który chcę przekształcić w obietnicę, wywołując c.fetch()metodę.

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

Więcej informacji: https://www.typescriptlang.org/docs/handbook/advanced-types.html

Murilo Perrone
źródło
6

Jeśli używasz metody asynchronicznej, możesz to zrobić i uniknąć niejasności.

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

Jeśli funkcja zwróci obietnicę, będzie oczekiwać i powróci z rozstrzygniętą wartością. Jeśli funkcja zwróci wartość, zostanie potraktowana jako rozwiązana.

Jeśli funkcja nie zwróci dziś obietnicy, ale jutro zwróci obietnicę lub zostanie uznana za asynchroniczną, będziesz zabezpieczony na przyszłość.

Steven Spungin
źródło
działa to, zgodnie z tym tutaj : „jeśli [oczekiwana] wartość nie jest obietnicą, [wyrażenie oczekujące] konwertuje wartość na rozstrzygniętą obietnicę i czeka na nią”
pqnet
Jest to w zasadzie to, co zostało zasugerowane w zaakceptowanej odpowiedzi, z wyjątkiem tego, że zamiast tego użyto składni asynchronicznej-czekajPromise.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));
});
czerwona kapusta
źródło
2

Używam tej funkcji jako uniwersalnego rozwiązania:

function isPromise(value) {
  return value && value.then && typeof value.then === 'function';
}
safrazik
źródło
-1

po znalezieniu niezawodnego sposobu na wykrycie funkcji asynchronicznych, a nawet obietnic , zakończyłem testem:

() => fn.constructor.name === 'Promise' || fn.constructor.name === 'AsyncFunction'
Sebastien H.
źródło
jeśli podklasujesz Promisei tworzysz tego wystąpienia, ten test może się nie powieść. powinno to jednak działać w przypadku większości tego, co próbujesz przetestować.
theram
Zgadzam się, ale nie rozumiem, dlaczego ktokolwiek miałby tworzyć podsieci obietnic
Sebastien H.
fn.constructor.name === 'AsyncFunction'jest źle - oznacza to, że coś jest funkcją asynchroniczną, a nie obietnicą - nie gwarantuje się też, że zadziała, ponieważ ludzie mogą podklasować obietnice
Benjamin Gruenbaum
@BenjaminGruenbaum Powyższy przykład działa w większości przypadków, jeśli utworzysz własną podklasę, powinieneś dodać testy do jej nazwy
Sebastien H.
Możesz, ale jeśli już wiesz, jakie to obiekty, już wiesz, czy rzeczy są obietnicami, czy nie.
Benjamin Gruenbaum
-3

ES6:

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

console.log(promise.toString().includes('Promise')); //true
Mathias Gheno Azzolini
źródło
2
Każdy obiekt, który ma (lub zastąpił) toStringmetodę, może po prostu zwrócić ciąg zawierający "Promise".
Boghyon Hoffmann
4
Ta odpowiedź jest zła z wielu powodów, z których najbardziej oczywistym jest'NotAPromise'.toString().includes('Promise') === true
cholery