Jak mogę synchronicznie określić stan JavaScript Promise?


149

Mam czystą obietnicę JavaScript (wbudowana implementacja lub poly-fill):

var promise = new Promise(function (resolve, reject) { /* ... */ });

Ze specyfikacji obietnica może być jedną z następujących:

  • „rozstrzygnięte” i „rozwiązane”
  • „rozstrzygnięte” i „odrzucone”
  • 'w oczekiwaniu'

Mam przypadek użycia, w którym chcę przesłuchać obietnicę synchronicznie i określić:

  • czy Obietnica jest spełniona?

  • jeśli tak, czy obietnica została rozwiązana?

Wiem, że mogę #then()zaplanować pracę, która ma być wykonywana asynchronicznie po zmianie stanu Promise. NIE pytam, jak to zrobić.

To pytanie dotyczy w szczególności synchronicznego badania stanu obietnicy . Jak mogę to osiągnąć?


6
ustaw właściwość obietnicy, którą można zobaczyć z zewnątrz, i użyj then (), aby zmienić właściwość.
dandavis

@jokeyrhyme FWIW, źródło V8 code.google.com/p/v8/source/browse/branches/bleeding_edge/src/... patrz var promiseStatus = NEW_PRIVATE("Promise#status");, PromiseSetfunkcji uSET_PRIVATE(promise, promiseStatus, status);
guest271314


Wydaje się dziwne, że jeśli zrobisz const a = Promise.resolve ('baz'); console.log (a); i spójrz na konsolę Chrome, zobaczysz Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "baz"} proto: Promise [[PromiseStatus]]: "resolved" [[PromiseValue]]: "baz ”a ludzie twierdzą, że nie da się tego zrobić. Jak Chrome to robi? (robiłem to w Plunkerze z Angular plnkr.co/edit/IPIWgLJKQStI5ubXmcsF
JGFMK

Użycie node 11.12.0 console.log pokaże stan obietnicy. EG console.log(Promise.new((resolve, reject) => {})=>Promise { <pending> }
Puhlze

Odpowiedzi:


77

Nie istnieje taki synchroniczny interfejs API inspekcji dla natywnych obietnic JavaScript. Nie da się tego zrobić z rodzimymi obietnicami. Specyfikacja nie określa takiej metody.

Biblioteki przestrzeni użytkownika mogą to zrobić, a jeśli celujesz w konkretny silnik (np. V8) i masz dostęp do kodu platformy (to znaczy możesz pisać kod w rdzeniu ), możesz użyć określonych narzędzi (takich jak symbole prywatne), aby to osiągnąć . Jest to jednak bardzo specyficzne i nie dotyczy przestrzeni użytkownika.


4
Uwaga: Szczerze wierzę, że przypadki użycia dla inspekcji synchronicznej są nieliczne i bardzo rzadkie, jeśli udostępnisz swój konkretny przypadek użycia w nowym pytaniu, pytając, jak to osiągnąć bez kontroli synchronicznej - dam odpowiedź, jeśli ktoś tego nie zrobi pokonaj mnie :)
Benjamin Gruenbaum

4
Nawet jeśli przypadki użycia są rzadkie, jakie szkody spowodowałoby włączenie czegoś takiego? Potrzebowałbym takiego sprawdzenia statusu, aby zobaczyć, czy poprzednia praca została zakończona i czy mogę poprosić o inną pracę. Nie mogę po prostu ustawić zmiennej zewnętrznej, ponieważ obiekt może zmienić właściciela bez powiadomienia. Co bardziej irytujące, mogę ZOBACZYĆ, że Node.js ma dostęp do tych informacji, ponieważ pokazuje mi je, kiedy je sprawdzam, ale nie ma innego sposobu, aby się do nich dostać poza analizowaniem ciągów?
Tustin2121

9
Musimy więc odrzucić rodzime obietnice, ponieważ są niepraktyczne i zawsze używamy niebieskiego ptaka. Dobre wieści! Jak zaproponować obietnice natywne, które staną się przestarzałe i zostaną usunięte z silnika węzłów?
user619271

1
Wiele rzeczy powinniśmy .anyzamiast tego spekulować i popełnić błąd, ponieważ Mark nalegał. Po pierwsze, Promise.race([])jest wiecznie oczekującą obietnicą (a nie błędem), zazwyczaj potrzebujesz pierwszej pomyślnej obietnicy, a nie tylko pierwszej. Zresztą, to nie jest tak naprawdę istotne dla zadanego pytania - OP zapytał o synchroniczną kontrolę, a nie o .racei jej liczne niedociągnięcia.
Benjamin Gruenbaum

5
@Akrikos, ta odpowiedź nie pozwala na synchroniczne sprawdzenie stanu obietnicy - na przykład MakeQueryablePromise(Promise.resolve(3)).isResolvedjest fałszywa, ale obietnica jest całkiem oczywista. Nie wspominając o tym, że w odpowiedzi niepoprawnie użyto również terminów „rozwiązany” i „spełniony”. Aby to zrobić, wystarczy, że .thensam dodasz program obsługi - co całkowicie mija się z punktem synchronicznej inspekcji.
Benjamin Gruenbaum

31

wprowadź opis obrazu tutaj

async-status-obietnicy załatwia sprawę. Jest asynchroniczny, ale nie thenoczekuje na rozwiązanie obietnicy.

const {promiseStatus} = require('promise-status-async');
// ...
if (await promiseStatus(promise) === 'pending') {
    const idle = new Promise(function(resolve) {
        // can do some IDLE job meanwhile
    });
    return idle;
}

4
OP zapytał jednak, jak to zrobić synchronicznie
Klesun

28

Nie, brak interfejsu API do synchronizacji, ale oto moja wersja async promiseState(z pomocą @Matthijs):

function promiseState(p) {
  const t = {};
  return Promise.race([p, t])
    .then(v => (v === t)? "pending" : "fulfilled", () => "rejected");
}

var a = Promise.resolve();
var b = Promise.reject();
var c = new Promise(() => {});

promiseState(a).then(state => console.log(state)); // fulfilled
promiseState(b).then(state => console.log(state)); // rejected
promiseState(c).then(state => console.log(state)); // pending


Czy istnieje jakieś konkretne uzasadnienie tej konstrukcji? Wydaje mi się to niepotrzebnie skomplikowane. O ile wiem, działa to identycznie: Promise.race([ Promise.resolve(p).then(() => "fulfilled", () => "rejected"), Promise.resolve().then(() => "pending") ]); Chociaż wydaje mi się to bezpieczniejsze: const t = {}; return Promise.race([p,t]).then(v => v === t ? "pending" : "fulfilled", () => "rejected") i pozwala uniknąć tworzenia dodatkowych obietnic, które są utrzymywane, dopóki oryginalne p jest w toku.
Matthijs

Dzięki @Matthijs! Uprościłem odpowiedź.
wysięgnik

16

Możesz zrobić wyścig z Promise.resolve
To nie jest synchroniczne, ale dzieje się teraz

function promiseState(p, isPending, isResolved, isRejected) {
  Promise.race([p, Promise.resolve('a value that p should not return')]).then(function(value) {
    if (value == 'a value that p should not return') {
      (typeof(isPending) === 'function') && isPending();
    }else {
      (typeof(isResolved) === 'function') && isResolved(value);
    }
  }, function(reason) {
    (typeof(isRejected) === 'function') && isRejected(reason);
  });
}

Mały skrypt do testowania i zrozumienia ich znaczenia asynchronicznie

var startTime = Date.now() - 100000;//padding trick "100001".slice(1) => 00001
function log(msg) {
  console.log((""+(Date.now() - startTime)).slice(1) + ' ' + msg);
  return msg;//for chaining promises
};

function prefix(pref) { return function (value) { log(pref + value); return value; };}

function delay(ms) {
  return function (value) {
    var startTime = Date.now();
    while(Date.now() - startTime < ms) {}
    return value;//for chaining promises
  };
}
setTimeout(log, 0,'timeOut 0 ms');
setTimeout(log, 100,'timeOut 100 ms');
setTimeout(log, 200,'timeOut 200 ms');

var p1 = Promise.resolve('One');
var p2 = new Promise(function(resolve, reject) { setTimeout(resolve, 100, "Two"); });
var p3 = Promise.reject("Three");

p3.catch(delay(200)).then(delay(100)).then(prefix('delayed L3 : '));

promiseState(p1, prefix('p1 Is Pending '), prefix('p1 Is Resolved '), prefix('p1 Is Rejected '));
promiseState(p2, prefix('p2 Is Pending '), prefix('p2 Is Resolved '), prefix('p2 Is Rejected '));
promiseState(p3, prefix('p3 Is Pending '), prefix('p3 Is Resolved '), prefix('p3 Is Rejected '));

p1.then(prefix('Level 1 : ')).then(prefix('Level 2 : ')).then(prefix('Level 3 : '));
p2.then(prefix('Level 1 : ')).then(prefix('Level 2 : ')).then(prefix('Level 3 : '));
p3.catch(prefix('Level 1 : ')).then(prefix('Level 2 : ')).then(prefix('Level 3 : '));
log('end of promises');
delay(100)();
log('end of script');

wyniki z opóźnieniem (0) (skomentuj czas opóźnienia)

00001 end of promises
00001 end of script
00001 Level 1 : One
00001 Level 1 : Three
00001 p1 Is Resolved One
00001 p2 Is Pending undefined
00001 p3 Is Rejected Three
00001 Level 2 : One
00001 Level 2 : Three
00001 delayed L3 : Three
00002 Level 3 : One
00002 Level 3 : Three
00006 timeOut 0 ms
00100 timeOut 100 ms
00100 Level 1 : Two
00100 Level 2 : Two
00101 Level 3 : Two
00189 timeOut 200 ms

i wyniki tego testu z firefoxem (chrome zachowaj kolejność)

00000 end of promises
00100 end of script
00300 Level 1 : One
00300 Level 1 : Three
00400 p1 Is Resolved One
00400 p2 Is Pending undefined
00400 p3 Is Rejected Three
00400 Level 2 : One
00400 Level 2 : Three
00400 delayed L3 : Three
00400 Level 3 : One
00400 Level 3 : Three
00406 timeOut 0 ms
00406 timeOut 100 ms
00406 timeOut 200 ms
00406 Level 1 : Two
00407 Level 2 : Two
00407 Level 3 : Two

promiseState make .race i. then: Level 2


3
Zamiast tego 'a value that p should not return'użyj Symbol
programmer5000

1
@ programmer5000 Jakie są korzyści?
Moritz Schmitz przeciwko Hülst

2
@ MoritzSchmitzv.Hülst a Symbolbyłaby unikalną wartością, dlatego nigdy nie musiałbyś zgadywać, której „wartość [...] p nie powinna zwracać”. Jednak równie dobrze działałoby odniesienie do określonego obiektu.
Scott Rudiger

7

Możesz użyć (brzydkiego) hacka w Node.js, dopóki nie zostanie zaoferowana metoda natywna:

util = require('util');

var promise1 = new Promise (function (resolve) {
}

var promise2 = new Promise (function (resolve) {

    resolve ('foo');
}

state1 = util.inspect (promise1);
state2 = util.inspect (promise2);

if (state1 === 'Promise { <pending> }') {

    console.log('pending'); // pending
}

if (state2 === "Promise { 'foo' }") {

    console.log ('foo') // foo
}

3
Sprowadziłem to do polyfill:Promise.prototype.isPending = function(){ return util.inspect(this).indexOf("<pending>")>-1; }
Tustin2121

5
To straszne .
John Weisz,

@JohnWeisz Przerażający to brak wstecznej kompatybilności. Próbuję zintegrować obiecujący interfejs API z bazą kodu, która zakłada, że ​​wszystko jest synchroniczne. To albo zrobienie czegoś strasznego, albo przepisanie ogromnych fragmentów kodu. Tak czy inaczej, popełniam okrucieństwo.
rath

4
po prostu użyjprocess.binding('util').getPromiseDetails
amara

@ Tustin2121 W przypadku niektórych wersji zakończy się niepowodzeniem z czymś takim jak Promise.resolve('<pending>').
user202729

7

w węźle, powiedzmy nieudokumentowane wewnętrzne process.binding('util').getPromiseDetails(promise)

> process.binding('util').getPromiseDetails(Promise.resolve({data: [1,2,3]}));
[ 1, { data: [ 1, 2, 3 ] } ]

> process.binding('util').getPromiseDetails(Promise.reject(new Error('no')));
[ 2, Error: no ]

> process.binding('util').getPromiseDetails(new Promise((resolve) => {}));
[ 0, <1 empty item> ]

Dodałem to, ponieważ nie było go w żadnej z istniejących odpowiedzi, a dla węzła jest to najlepsza odpowiedź. łatwo jest sprawdzić dokumentację na github.com/nodejs/node
amara

6

Zaktualizowano: 2019

Bluebird.js oferuje to: http://bluebirdjs.com/docs/api/isfulfilled.html

var Promise = require("bluebird");
let p = Promise.resolve();
console.log(p.isFulfilled());

Jeśli wolisz stworzyć własne opakowanie, oto fajny blog o tym.

Ponieważ JavaScript jest jednowątkowy, trudno jest znaleźć wystarczająco powszechny przypadek użycia, aby uzasadnić umieszczenie go w specyfikacji. Najlepszym miejscem, aby dowiedzieć się, czy obietnica zostanie rozwiązana, jest .then (). Testowanie, czy obietnica jest wypełniona, stworzyłoby pętlę odpytywania, która najprawdopodobniej jest w złym kierunku.

async / await to fajna konstrukcja, jeśli chcesz synchronicznie rozumować kod asynchroniczny.

await this();
await that();
return 'success!';

Innym przydatnym wywołaniem jest Promise.all ()

var promise1 = Promise.resolve(3);
var promise2 = 42;
var promise3 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then(function(values) {
  console.log(values);
});
// expected output: Array [3, 42, "foo"]

Kiedy po raz pierwszy sięgnąłem po tę odpowiedź, to jest przypadek użycia, którego szukałem.


5

W ten sposób możesz zawrzeć swoje obietnice

function wrapPromise(promise) {
  var value, error,
      settled = false,
      resolved = false,
      rejected = false,
      p = promise.then(function(v) {
        value = v;
        settled = true;
        resolved = true;
        return v;
      }, function(err) {
        error = err;
        settled = true;
        rejected = true;
        throw err;
      });
      p.isSettled = function() {
        return settled;
      };
      p.isResolved = function() {
        return resolved;
      };
      p.isRejected = function() {
        return rejected;
      };
      p.value = function() {
        return value;
      };
      p.error = function() {
        return error;
      };
      var pThen = p.then, pCatch = p.catch;
      p.then = function(res, rej) {
        return wrapPromise(pThen(res, rej));
      };
      p.catch = function(rej) {
        return wrapPromise(pCatch(rej));
      };
      return p;
}

5
Wymagałoby to od OP uzyskania dostępu do obietnicy w poprzedniej turze pętli zdarzeń . Ponieważ .thenzawsze wykonuje asynchronicznie OP, który chce sprawdzić obietnicę w tej samej turze , nie uzyska tutaj poprawnego wyniku. Uwaga OP zapytał konkretnie o inspekcję synchroniczną i wspomniał, że wie już o inspekcji asynchronicznej.
Benjamin Gruenbaum

@BenjaminGruenbaum: czy nie pojawiłyby się wartości domyślne, gdyby kod na tej samej „turze” to wywołał?
dandavis

Oczywiście musiałbyś zawrzeć wszystkie swoje obietnice w czasie tworzenia. np. wewnątrz funkcji, które je tworzą i zwracają.
SpiderPig

3
Racja, w którym momencie nie są już tak naprawdę natywnymi obietnicami, równie dobrze możesz je rozszerzyć w taki sposób, w jaki mają być rozszerzone, o podklasy, które pozwolą ci to zrobić elegancko zamiast małpy łatać właściwości obiektu.
Benjamin Gruenbaum

Niezależnie od tego, czy przedłużysz obietnicę w sposób, w jaki pokazałem, czy przez podklasyfikowanie, w każdym przypadku nadal będziesz musiał dodać swoją własną wersję i złapać.
SpiderPig

5

To rzeczywiście dość denerwujące, że brakuje tej podstawowej funkcji. Jeśli używasz node.js, to znam dwa obejścia, żadne z nich nie są zbyt ładne. Oba poniższe fragmenty implementują ten sam interfejs API:

> Promise.getInfo( 42 )                         // not a promise
{ status: 'fulfilled', value: 42 }
> Promise.getInfo( Promise.resolve(42) )        // fulfilled
{ status: 'fulfilled', value: 42 }
> Promise.getInfo( Promise.reject(42) )         // rejected
{ status: 'rejected', value: 42 }
> Promise.getInfo( p = new Promise(() => {}) )  // unresolved
{ status: 'pending' }
> Promise.getInfo( Promise.resolve(p) )         // resolved but pending
{ status: 'pending' }

Wydaje się, że nie ma sposobu na rozróżnienie dwóch ostatnich stanów obietnicy za pomocą którejkolwiek ze sztuczek.

1. Użyj interfejsu API do debugowania V8

To ta sama sztuczka, której util.inspectużywa.

const Debug = require('vm').runInDebugContext('Debug');

Promise.getInfo = function( arg ) {
    let mirror = Debug.MakeMirror( arg, true );
    if( ! mirror.isPromise() )
        return { status: 'fulfilled', value: arg };
    let status = mirror.status();
    if( status === 'pending' )
        return { status };
    if( status === 'resolved' )  // fix terminology fuck-up
        status = 'fulfilled';
    let value = mirror.promiseValue().value();
    return { status, value };
};

2. Synchronicznie uruchom mikrozadania

Pozwala to uniknąć debugowania API, ale ma przerażającą semantykę, powodując process.nextTicksynchroniczne uruchamianie wszystkich oczekujących mikrozadań i wywołań zwrotnych. Ma to również efekt uboczny polegający na zapobieganiu wywoływaniu błędu „nieobsłużonego odrzucenia obietnicy” dla sprawdzonej obietnicy.

Promise.getInfo = function( arg ) {
    const pending = {};
    let status, value;
    Promise.race([ arg, pending ]).then(
        x => { status = 'fulfilled'; value = x; },
        x => { status = 'rejected'; value = x; }
    );
    process._tickCallback();  // run microtasks right now
    if( value === pending )
        return { status: 'pending' };
    return { status, value };
};

Jest to bardzo niebezpieczne process._tickCallback(lub nawet zwykły% RunMicrotick) - losowo zepsuje rzeczy w kodzie. Desperacko próbowałem go uruchomić (głównie dla fałszywych timerów w funkcjach asynchronicznych) i nigdy nie był wystarczająco stabilny od strony węzła. Zrezygnowałem z pracy nad tym. Interfejs API lustrzanego debugowania V8 jest tutaj całkowicie odpowiedni.
Benjamin Gruenbaum

I ... DeprecationWarning: DebugContext has been deprecated and will be removed in a future version.:( Wygląda na to, że V8 go usunęło
Benjamin Gruenbaum,

My (Node) możemy całkowicie poprosić V8 o API lub ujawnić API do bezpośredniego spojrzenia na stan obietnicy - jeśli otworzysz problem na github.com/nodejs/promise-use-cases, z radością poruszę go z V8
Benjamin Gruenbaum

1
Komentarz w dalszej części tego tematu ujawnił, że API wydaje się już istnieć: process.binding('util').getPromiseDetails( promise )zwraca w [ 0, ]przypadku oczekujących, [ 1, value ]wypełnionych i [ 2, value ]odrzuconych.
Matthijs

3

Uwaga: ta metoda wykorzystuje nieudokumentowane elementy wewnętrzne Node.js i może zostać zmieniona bez ostrzeżenia.

W Node możesz synchronicznie określić stan obietnicy za pomocą process.binding('util').getPromiseDetails(/* promise */);.

To zwróci:

[0, ] w toku,

[1, /* value */] za spełnione lub

[2, /* value */] za odrzucone.

const pending = new Promise(resolve => setTimeout(() => resolve('yakko')));;
const fulfilled = Promise.resolve('wakko');
const rejected = Promise.reject('dot');

[pending, fulfilled, rejected].forEach(promise => {
  console.log(process.binding('util').getPromiseDetails(promise));
});

// pending:   [0, ]
// fulfilled: [1, 'wakko']
// rejected:  [2, 'dot']

Pakowanie tego w funkcję pomocniczą:

const getStatus = promise => ['pending', 'fulfilled', 'rejected'][
  process.binding('util').getPromiseDetails(promise)[0]
];

getStatus(pending); // pending
getStatus(fulfilled); // fulfilled
getStatus(rejected); // rejected

Wydaje się, że nie działa od wewnątrz jest(co jest jedynym miejscem, w którym się nim interesuję). Funkcja istnieje, ale zawsze wydaje się powracać undefined. Jak dowiedzieć się, co się stało?
Adam Barnes

Hmm, pamiętam, że to działało w środku mocha; nigdy tego nie próbowałem jest. Może zacznij nowe pytanie, łącząc się tutaj i dołącz swoją wersję Node.js, a także jestwersję?
Scott Rudiger

Niestety, nie jest to już coś, co mnie bardzo interesuje. Zasadniczo chciałem przetestować poczytalność mojego ręcznie rozwiązywalnego / odrzucalnego, Promisektórego używałem tylko do testowania rzeczy, które powinny się dziać w Promisetoku, ale doszedłem do wniosku, że dopóki to, co napisałem, działa, nie ma potrzeby tego testować oprócz tego, co na tym polega.
Adam Barnes

2

co możesz zrobić, to użyć zmiennej do przechowywania stanu, ręcznie ustawić stan na tę zmienną i sprawdzić tę zmienną.

var state = 'pending';

new Promise(function(ff, rjc) {
  //do something async

  if () {//if success
    state = 'resolved';

    ff();//
  } else {
    state = 'rejected';

    rjc();
  }
});

console.log(state);//check the state somewhere else in the code

oczywiście oznacza to, że musisz mieć dostęp do oryginalnego kodu promesy. Jeśli tego nie zrobisz, możesz:

var state = 'pending';

//you can't access somePromise's code
somePromise.then(function(){
  state = 'resolved';
}, function() {
  state = 'rejected';
})

console.log(state);//check the promise's state somewhere else in the code

Moje rozwiązanie to bardziej kodowanie, ale myślę, że prawdopodobnie nie musiałbyś tego robić dla każdej obietnicy, której używasz.


2

Począwszy od wersji 8 Node.js, możesz teraz używać pakietu Wise-Inspection do synchronicznego sprawdzania natywnych obietnic (bez niebezpiecznych hacków).


2

Możesz dodać metodę do Promise.prototype. To wygląda tak:

Edytowano: pierwsze rozwiązanie nie działa poprawnie, podobnie jak większość odpowiedzi tutaj. Zwraca wartość „pending” do momentu wywołania funkcji asynchronicznej „.then”, co nie następuje natychmiast. (To samo dotyczy rozwiązań wykorzystujących Promise.race). Moje drugie rozwiązanie rozwiązuje ten problem.

if (window.Promise) {
    Promise.prototype.getState = function () {
        if (!this.state) {
            this.state = "pending";
            var that = this;
            this.then(
                function (v) {
                    that.state = "resolved";
                    return v;
                },
                function (e) {
                    that.state = "rejected";
                    return e;
                });
        }
        return this.state;
    };
}

Możesz go użyć na dowolnej obietnicy. Na przykład:

myPromise = new Promise(myFunction);
console.log(myPromise.getState()); // pending|resolved|rejected

Drugie (i poprawne) rozwiązanie:

if (window.Promise) {
    Promise.stateable = function (func) {
        var state = "pending";
        var pending = true;
        var newPromise = new Promise(wrapper);
        newPromise.state = state;
        return newPromise;
        function wrapper(resolve, reject) {
            func(res, rej);
            function res(e) {
                resolve(e);
                if (pending) {
                    if (newPromise)
                        newPromise.state = "resolved";
                    else
                        state = "resolved";
                    pending = false;
                }
            }
            function rej(e) {
                reject(e);
                if (pending) {
                    if (newPromise)
                        newPromise.state = "rejected";
                    else
                        state = "rejected";
                    pending = false;
                }
            }
        }
    };
}

I użyj go:

Uwaga : w tym rozwiązaniu nie musisz używać operatora „new”.

myPromise = Promise.stateable(myFunction);
console.log(myPromise.state); // pending|resolved|rejected

1

Oto bardziej dopracowana wersja QueryablePromise es6, umożliwiająca łączenie i przechwytywanie po pierwszym rozwiązaniu oraz natychmiastowe rozstrzygnięcie lub odrzucenie, aby interfejs API był zgodny z natywną obietnicą.

const PROMISE = Symbol('PROMISE')
const tap = fn => x => (fn(x), x)
const trace = label => tap(x => console.log(label, x))

class QueryablePromise {
  resolved = false
  rejected = false
  fulfilled = false
  catchFns = []
  constructor(fn) {
    this[PROMISE] = new Promise(fn)
      .then(tap(() => {
        this.fulfilled = true
        this.resolved = true
      }))
      .catch(x => {
        this.fulfilled = true
        this.rejected = true
        return Promise.reject(x)
      })
  }
  then(fn) {
    this[PROMISE].then(fn)
    return this
  }
  catch(fn) {
    this[PROMISE].catch(fn)
    return this
  }
  static resolve(x) {
    return new QueryablePromise((res) => res(x))
  }
  static reject(x) {
    return new QueryablePromise((_, rej) => rej(x))
  }
}

const resolvedPromise = new QueryablePromise((res) => {
  setTimeout(res, 200, 'resolvedPromise')
})

const rejectedPromise = new QueryablePromise((_, rej) => {
  setTimeout(rej, 200, 'rejectedPromise')
})

// ensure our promises have not been fulfilled
console.log('test 1 before: is resolved', resolvedPromise.resolved)
console.log('test 2 before: is rejected', rejectedPromise.rejected)


setTimeout(() => {
  // check to see the resolved status of our promise
  console.log('test 1 after: is resolved', resolvedPromise.resolved)
  console.log('test 2 after: is rejected', rejectedPromise.rejected)
}, 300)

// make sure we can immediately resolve a QueryablePromise
const immediatelyResolvedPromise = QueryablePromise.resolve('immediatelyResolvedPromise')
  // ensure we can chain then
  .then(trace('test 3 resolved'))
  .then(trace('test 3 resolved 2'))
  .catch(trace('test 3 rejected'))

// make sure we can immediately reject a QueryablePromise
const immediatelyRejectedPromise = QueryablePromise.reject('immediatelyRejectedPromise')
  .then(trace('test 4 resolved'))
  .catch(trace('test 4 rejected'))
<script src="https://codepen.io/synthet1c/pen/KyQQmL.js"></script>


1

awaitużycie do odpowiedzi @ jib , z idiomatycznym prototypowaniem.

Object.defineProperty(Promise.prototype, "state", {
    get: function(){
        const o = {};
        return Promise.race([this, o]).then(
            v => v === o ? "pending" : "resolved",
            () => "rejected");
    }
});

// usage: console.log(await <Your Promise>.state);
(async () => {
    console.log(await Promise.resolve(2).state);  // "resolved"
    console.log(await Promise.reject(0).state);   // "rejected"
    console.log(await new Promise(()=>{}).state); // "pending"
})();

zwróć uwagę, że ta funkcja asynchroniczna jest wykonywana „prawie” natychmiast, jak funkcja zsynchronizowana (a właściwie prawdopodobnie natychmiast).


1

2019:

Najprostszym sposobem na zrobienie tego, jak wiem, jest thenablesuper cienkie opakowanie wokół obietnicy lub dowolna praca asynchroniczna.

const sleep = (t) => new Promise(res => setTimeout(res,t));
const sleeping = sleep(30);

function track(promise){
    let state = 'pending';
    promise = promise.finally( _=> state ='fulfilled');
    return {
        get state(){return state},
        then: promise.then.bind(promise), /*thentable*/
        finally:promise.finally.bind(promise),
        catch:promise.catch.bind(promise),
    }
}


promise = track(sleeping);
console.log(promise.state) // pending

promise.then(function(){
    console.log(promise.state); // fulfilled
})

1

Możesz użyć extendklasy Promise, aby utworzyć nową klasę Promise, z możliwością odpytywania .

Możesz utworzyć własną podklasę, powiedzmy QueryablePromise, dziedzicząc z Promiseklasy dostępnej natywnie , której instancje miałyby statusdostępną właściwość, której można użyć do synchronicznego sprawdzania stanu obiektów obietnicy . Implementacja nim można zobaczyć poniżej lub skierować to do lepszego wyjaśnienia.

class QueryablePromise extends Promise {
  constructor (executor) {
    super((resolve, reject) => executor(
      data => {
        resolve(data)
        this._status = 'Resolved'
      },
      err => {
        reject(err)
        this._status = 'Rejected'
      },
    ))
    this._status = 'Pending'
  }

  get status () {
    return this._status
  }
}
 
// Create a promise that resolves after 5 sec 
var myQueryablePromise = new QueryablePromise((resolve, reject) => {
  setTimeout(() => resolve(), 5000)
})

// Log the status of the above promise every 500ms
setInterval(() => {
  console.log(myQueryablePromise.status)
}, 500)


Niestety żaden istniejący interfejs API nie zwróci tej nowej klasy. Jak sobie wyobrażasz, jak ludzie tego używają?
wysięgnik

@jib Dzięki za twoją odpowiedź. Co masz na myśli, mówiąc, że żaden interfejs API nie zwróciłby tej klasy? :(
UtkarshPramodGupta

Żadne istniejące interfejsy API go nie zwrócą, ponieważ musiałyby zostać napisane, aby je zwrócić, prawda? Np. Jeśli zadzwonię fetch, zwróci rodzimą obietnicę. Jak twoja klasa pomogłaby w tym?
wysięgnik

Cóż, nie możemy po prostu owinąć że sprowadzić rozmowę w naszym nowym QuerablePromise jak: const queryableFetch = new QueryablePromise((resolve, reject) => {fetch(/.../).then((data) => resolve(data)) })? A może jest z tym jakiś problem? : /
UtkarshPramodGupta

To powinno zadziałać, po prostu nie zapomnij o tym, że , err => reject(err)jako drugi argument thennie będzie poprawnie propagował błędów (między powodami jest uważany za anty-wzorzec konstruktora obietnicy ). Nie jest to jednak naprawdę synchroniczne (np. Nie wykryje już rozwiązanej obietnicy), ale może być przydatne w przypadkach, gdy nie kontrolujesz dzwoniącego, a odpowiedź jest potrzebna natychmiast.
wysunięcie

1

Istnieje inny elegancki i hacky sposób sprawdzenia, czy obietnica nadal oczekuje, po prostu konwertując cały obiekt na łańcuch i sprawdzając go za pomocą inspekcji w następujący sposób:util.inspect(myPromise).includes("pending") .

Testowane na Node.js 8,9,10,11,12,13

Oto pełny przykład

const util = require("util")

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

(async ()=>{
  let letmesleep = sleep(3000)
  setInterval(()=>{
    console.log(util.inspect(letmesleep).includes("pending"))
  },1000)
})()

Wynik:

true
true
false
false
false

0

Jeśli używasz eksperymentalnego ES7, możesz użyć asynchronicznego, aby łatwo zawrzeć obietnicę, której chcesz słuchać.

async function getClient() {
  let client, resolved = false;
  try {
    client = await new Promise((resolve, reject) => {
      let client = new Client();

      let timer = setTimeout(() => {
         reject(new Error(`timeout`, 1000));
         client.close();
      });

      client.on('ready', () => {
        if(!resolved) {
          clearTimeout(timer);
          resolve(client);
        }
      });

      client.on('error', (error) => {
        if(!resolved) {
          clearTimeout(timer);
          reject(error);
        }
      });

      client.on('close', (hadError) => {
        if(!resolved && !hadError) {
          clearTimeout(timer);
          reject(new Error("close"));
        }
      });
    });

    resolved = true;
  } catch(error) {
    resolved = true;
    throw error;
  }
  return client;
}


0

To jest starsze pytanie, ale próbowałem zrobić coś podobnego. Muszę podtrzymać pracę n pracowników. Są zorganizowane w obietnicę. Muszę zeskanować i sprawdzić, czy zostały rozwiązane, odrzucone lub nadal oczekują. Jeśli problem zostanie rozwiązany, potrzebuję wartości, a jeśli zostanie odrzucona, zrób coś, aby rozwiązać problem lub oczekuję. Jeśli zostanie rozwiązany lub odrzucony, muszę rozpocząć kolejne zadanie, aby kontynuować. Nie mogę wymyślić sposobu, aby to zrobić z Promise.all lub Promise.race, ponieważ nadal pracuję z obietnicami w tablicy i nie mogę znaleźć sposobu, aby je usunąć. Więc tworzę pracownika, który załatwia sprawę

Potrzebuję funkcji generatora obietnic, która zwraca obietnicę, która w razie potrzeby rozpatruje lub odrzuca. Jest wywoływana przez funkcję, która tworzy ramy, aby wiedzieć, co robi obietnica.

W poniższym kodzie generator po prostu zwraca obietnicę na podstawie setTimeout.

Tutaj jest

//argObj should be of form
// {succeed: <true or false, nTimer: <desired time out>}
function promiseGenerator(argsObj) {
  let succeed = argsObj.succeed;          
  let nTimer = argsObj.nTimer;
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (succeed) {
        resolve('ok');
      }
      else {
        reject(`fail`);
      }
    }, nTimer);
  })

}

function doWork(generatorargs) {
  let sp = { state: `pending`, value: ``, promise: "" };
  let p1 = promiseGenerator(generatorargs)
    .then((value) => {
      sp.state = "resolved";
      sp.value = value;
    })
    .catch((err) => {
      sp.state = "rejected";
      sp.value = err;
    })
  sp.promise = p1;
  return sp;
}

doWork zwraca obiekt zawierający przyrzeczenie i jego stan oraz zwróconą wartość.

Poniższy kod uruchamia pętlę, która testuje stan i tworzy nowe procesy robocze, aby zachować 3 uruchomione procesy robocze.

let promiseArray = [];

promiseArray.push(doWork({ succeed: true, nTimer: 1000 }));
promiseArray.push(doWork({ succeed: true, nTimer: 500 }));
promiseArray.push(doWork({ succeed: false, nTimer: 3000 }));

function loopTimerPromise(delay) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('ok');
    }, delay)
  })
}

async function looper() {
  let nPromises = 3;      //just for breaking loop
  let nloop = 0;          //just for breaking loop
  let i;
  //let continueLoop = true;
  while (true) {
    await loopTimerPromise(900);  //execute loop every 900ms
    nloop++;
    //console.log(`promiseArray.length = ${promiseArray.length}`);
    for (i = promiseArray.length; i--; i > -1) {
      console.log(`index ${i} state: ${promiseArray[i].state}`);
      switch (promiseArray[i].state) {
        case "pending":
          break;
        case "resolved":
          nPromises++;
          promiseArray.splice(i, 1);
          promiseArray.push(doWork({ succeed: true, nTimer: 1000 }));
          break;
        case "rejected":
          //take recovery action
          nPromises++;
          promiseArray.splice(i, 1);
          promiseArray.push(doWork({ succeed: false, nTimer: 500 }));
          break;
        default:
          console.log(`error bad state in i=${i} state:${promiseArray[i].state} `)
          break;
      }
    }
    console.log(``);
    if (nloop > 10 || nPromises > 10) {
      //should do a Promise.all on remaining promises to clean them up but not for test
      break;
    }
  }
}

looper();

Przetestowano w node.js.

Przy okazji, nie w tej odpowiedzi tak bardzo, ale w innych na podobne tematy NIENAWIDZĘ, gdy ktoś mówi „nie rozumiesz” lub „tak to nie działa”. Generalnie zakładam, że pytający wie, czego chce. Sugerowanie lepszego sposobu jest świetne. Cierpliwe wyjaśnienie, jak działają obietnice, również byłoby dobre.


-1

Okazało się, że to rozwiązanie jest proste i pozwala mi nadal korzystać z natywnych obietnic, ale dodać przydatne kontrole synchroniczne. Nie musiałem też ściągać całej biblioteki obietnic.

OSTROŻNIE: Działa to tylko wtedy, gdy w bieżącym wątku wykonawczym występuje przerwa, aby umożliwić wykonanie obietnic PRZED sprawdzeniem konstrukcji synchronicznych. To sprawia, że ​​ma to bardziej ograniczoną użyteczność, niż początkowo sądziłem - nadal jest przydatna w moim przypadku użycia (dzięki Benjaminowi Gruenbaumowi za wskazanie tego)

/**
 * This function allow you to modify a JS Promise by adding some status properties.
 * Based on: http://stackoverflow.com/questions/21485545/is-there-a-way-to-tell-if-an-es6-promise-is-fulfilled-rejected-resolved
 * But modified according to the specs of promises : https://promisesaplus.com/
 */
function MakeQuerablePromise(promise) {
    // Don't modify any promise that has been already modified.
    if (promise.isFulfilled) return promise;

    // Set initial state
    var isPending = true;
    var isRejected = false;
    var isFulfilled = false;

    // Observe the promise, saving the fulfillment in a closure scope.
    var result = promise.then(
        function(v) {
            isFulfilled = true;
            isPending = false;
            return v; 
        }, 
        function(e) {
            isRejected = true;
            isPending = false;
            throw e; 
        }
    );

    result.isFulfilled = function() { return isFulfilled; };
    result.isPending = function() { return isPending; };
    result.isRejected = function() { return isRejected; };
    return result;
}

wrappedPromise = MakeQueryablePromise(Promise.resolve(3)); 
setTimeout(function() {console.log(wrappedPromise.isFulfilled())}, 1);

Z https://ourcodeworld.com/articles/read/317/how-to-check-if-a-javascript-promise-has-been-fulfilled-rejected-or-resolved, który oparł swoją odpowiedź na Czy istnieje sposób na powiedzieć, czy obietnica ES6 została spełniona / odrzucona / rozwiązana?


Jak dodałeś w swoim komentarzu do mojej odpowiedzi - jest to całkowicie niepoprawne: to nie pozwala na synchroniczne sprawdzenie stanu obietnicy - Na przykład MakeQueryablePromise(Promise.resolve(3)).isResolvedjest fałszywa, ale obietnica jest całkiem oczywista. Nie wspominając o tym, że w odpowiedzi niepoprawnie użyto również terminów „rozwiązany” i „spełniony”. Aby to zrobić, wystarczy, że .thensam dodasz program obsługi - co całkowicie mija się z punktem synchronicznej inspekcji.
Benjamin Gruenbaum,

Rozumiem, co mówisz, i masz rację. Jednowątkowy charakter JS przeszkadza, prawda? Musisz przerwać bieżącą realizację, aby obietnica została oznaczona jako rozwiązana. let wrappedPromise = MakeQueryablePromise(Promise.resolve(3)); setTimeout(function() {console.log(wrappedPromise.isFulfilled())}, 1);Co tak długo, jak to robisz, działa dobrze. Ale musisz zrozumieć ten fakt, aby było to przydatne. Zaktualizuję opis z tym zastrzeżeniem. Zgadzam się też, że nazewnictwo funkcji mogłoby być lepsze / bardziej idiomatyczne.
Akrikos,

Ale w tym momencie możesz po prostu thenoryginalną obietnicę i osiągnąć to samo, ponieważ i tak jest asynchroniczna. Jest na to sposób process.binding('util').getPromiseDetails, który wydaje się działać, ale korzysta z prywatnego interfejsu API
Benjamin Gruenbaum

To okropne, że trzeba to robić przez cały czas i sprawia, że ​​kod jest znacznie trudniejszy do zrozumienia. Zwłaszcza, gdy wszystko, na czym mi zależy, to to, czy obietnica została odrzucona, czy nie - więc mam możliwość albo przechowywać ten stan w innym miejscu, albo zrobić coś takiego. Przyznaję, że nie przeczytałem dokładnie pozostałych rozwiązań przed zamieszczeniem własnych - przepraszam za to. Ten problem jest trudniejszy niż myślałem na początku.
Akrikos,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.