Wie kann ich den Status eines JavaScript-Versprechens synchron bestimmen?

149

Ich habe ein reines JavaScript-Versprechen (integrierte Implementierung oder Poly-Fill):

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

Aus der Spezifikation kann ein Versprechen eines der folgenden sein:

  • "erledigt" und "gelöst"
  • "erledigt" und "abgelehnt"
  • 'steht aus'

Ich habe einen Anwendungsfall, in dem ich das Versprechen synchron abfragen und Folgendes bestimmen möchte:

  • Ist das Versprechen erfüllt?

  • Wenn ja, ist das Versprechen gelöst?

Ich weiß, dass ich #then()damit planen kann, dass Arbeiten asynchron ausgeführt werden, nachdem der Status "Versprechen" geändert wurde. Ich frage NICHT, wie das geht.

Diese Frage betrifft speziell die synchrone Abfrage des Status eines Versprechens . Wie kann ich das erreichen?

Scherz
quelle
6
Setzen Sie eine Eigenschaft auf das Versprechen, die von außen sichtbar ist, und verwenden Sie dann (), um die Eigenschaft zu ändern.
Dandavis
@jokeyrhyme FWIW, v8 Quelle code.google.com/p/v8/source/browse/branches/bleeding_edge/src/... sehen var promiseStatus = NEW_PRIVATE("Promise#status");, PromiseSetFunktion anSET_PRIVATE(promise, promiseStatus, status);
guest271314
Los
Jokeyrhyme
Es scheint seltsam, dass wenn Sie const a = Promise.resolve ('baz'); console.log (a); und schauen Sie in der Chrome-Konsole, Sie sehen Promise {[[PromiseStatus]]: "gelöst", [[PromiseValue]]: "baz"} proto: Promise [[PromiseStatus]]: "gelöst" [[PromiseValue]]: "baz "und die Leute behaupten, dass es nicht geht. Wie macht Chrome das? (tat dies in einem Plunker mit Angular plnkr.co/edit/IPIWgLJKQStI5ubXmcsF
JGFMK
Wenn Sie den Knoten v11.12.0 verwenden, zeigt console.log den Versprechungsstatus an. EG console.log(Promise.new((resolve, reject) => {})=>Promise { <pending> }
Puhlze

Antworten:

77

Für native JavaScript-Versprechen gibt es keine solche synchrone Inspektions-API. Es ist unmöglich, dies mit einheimischen Versprechungen zu tun. Die Spezifikation spezifiziert keine solche Methode.

Userland-Bibliotheken können dies tun. Wenn Sie auf eine bestimmte Engine (wie v8) abzielen und Zugriff auf Plattformcode haben (dh Sie können Code in den Kern schreiben ), können Sie bestimmte Tools (wie private Symbole) verwenden, um dies zu erreichen . Das ist allerdings super spezifisch und nicht im Userland.

Benjamin Gruenbaum
quelle
4
Hinweis: Ich bin der festen Überzeugung, dass es nur wenige und sehr seltene Anwendungsfälle für die synchrone Inspektion gibt, wenn Sie Ihren konkreten Anwendungsfall in einer neuen Frage mit der Frage teilen , wie dies ohne synchrone Inspektion erreicht werden kann. Ich werde es versuchen, wenn jemand dies nicht tut schlagen Sie mich dazu :)
Benjamin Gruenbaum
4
Selbst wenn die Anwendungsfälle selten sind, welchen Schaden würde ein solcher Schaden anrichten? Ich würde eine solche Statusprüfung benötigen, um festzustellen, ob der vorherige Job beendet wurde und ob ich einen anderen Job anfordern kann. Und ich kann nicht einfach eine externe Variable festlegen, da das Objekt das Potenzial hat, den Besitzer ohne vorherige Ankündigung zu wechseln. Was irritierender ist, ist, dass ich SEHEN kann, dass Node.js Zugriff auf diese Informationen hat, weil sie mir bei der Überprüfung angezeigt werden, aber es gibt keine Möglichkeit, darauf zuzugreifen, außer Zeichenfolgen zu analysieren?
Tustin2121
9
Wir müssen also einheimische Versprechen wegwerfen, da sie unpraktisch sind und immer Bluebird verwenden. Großartige Neuigkeiten! Wie schlage ich native Versprechen vor, veraltet und aus der Node Engine geworfen zu werden?
user619271
1
Viele Dinge, wir hätten .anystattdessen sprechen und einen Fehler machen sollen, weil Mark darauf bestand. Zum einen Promise.race([])ist ein für immer anstehendes Versprechen (und kein Fehler), Sie möchten normalerweise das erste erfolgreiche Versprechen und nicht nur das erste Versprechen. Auf jeden Fall ist das für die gestellte Frage nicht wirklich relevant - OP fragte nach synchroner Inspektion und nicht nach .raceund seinen vielen Mängeln.
Benjamin Gruenbaum
5
@Akrikos Mit dieser Antwort können Sie den Status eines Versprechens nicht synchron überprüfen. Zum Beispiel MakeQueryablePromise(Promise.resolve(3)).isResolvedist es falsch, aber das Versprechen ist ganz offensichtlich gelöst. Ganz zu schweigen davon, dass in dieser Antwort auch der Begriff "gelöst" und "erfüllt" falsch verwendet wird. Um dies zu erreichen, können Sie einfach .thenselbst einen Handler hinzufügen - was den Punkt der synchronen Inspektion völlig verfehlt.
Benjamin Gruenbaum
31

Geben Sie hier die Bildbeschreibung ein

Versprechen-Status-Async macht den Trick. Es ist asynchron, wird jedoch nicht verwendet, thenum auf die Lösung des Versprechens zu warten.

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;
}
0xaB
quelle
4
OP fragte, wie es synchron gemacht werden soll
Klesun
28

Nein, keine Synchronisierungs-API, aber hier ist meine Version der Asynchronisierung promiseState(mit Hilfe von @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

Ausleger
quelle
Gibt es eine bestimmte Begründung für diese Konstruktion? Es scheint mir unnötig kompliziert. Soweit ich das beurteilen kann, funktioniert dies identisch: Promise.race([ Promise.resolve(p).then(() => "fulfilled", () => "rejected"), Promise.resolve().then(() => "pending") ]); Obwohl mir dies sicherer erscheint: const t = {}; return Promise.race([p,t]).then(v => v === t ? "pending" : "fulfilled", () => "rejected") und vermeidet es, zusätzliche Versprechen zu erstellen, die bestehen bleiben, solange das ursprüngliche p aussteht.
Matthijs
Danke @Matthijs! Ich habe meine Antwort vereinfacht.
Fock
16

Sie können ein Rennen mit Promise.resolve machen.
Es ist nicht synchron, aber es passiert jetzt

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

Ein kleines Skript zum Testen und Verstehen ihrer Bedeutung von asynchron

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');

Ergebnisse mit Verzögerung (0) (Kommentar während der Verzögerung)

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

und die Ergebnisse dieses Tests mit Firefox (Chrom halten die Reihenfolge)

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

versprechenStaat machen .race und .then: Level 2

Steween
quelle
3
'a value that p should not return'Verwenden Sie stattdessen ein Symbol
programmer5000
1
@ programmer5000 Was ist der Vorteil?
Moritz Schmitz v. Hülst
2
@ MoritzSchmitzv.Hülst a Symbolwäre ein eindeutiger Wert, daher müsste man nie raten, welcher "Wert [...] p nicht zurückkehren sollte". Ein Verweis auf ein bestimmtes Objekt würde jedoch genauso gut funktionieren.
Scott Rüdiger
7

Sie können einen (hässlichen) Hack in Node.js verwenden, bis eine native Methode angeboten wird:

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
}
rabbitco
quelle
3
Ich habe es auf eine Promise.prototype.isPending = function(){ return util.inspect(this).indexOf("<pending>")>-1; }
Polyfüllung reduziert
5
Das ist schrecklich .
John Weisz
@ JohnWeisz Was schrecklich ist, ist die mangelnde Abwärtskompatibilität. Ich versuche, eine vielversprechende API in eine Codebasis zu integrieren, die davon ausgeht, dass alles synchron ist. Entweder macht es etwas Schreckliches oder es schreibt riesige Codestücke neu. In jedem Fall begebe ich eine Gräueltat.
Rath
4
benutze einfachprocess.binding('util').getPromiseDetails
amara
@ Tustin2121 Für einige Versionen wird es mit so etwas fehlschlagen Promise.resolve('<pending>').
user202729
7

Sagen Sie im Knoten undokumentiert intern 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> ]
Amara
quelle
Ich habe dies hinzugefügt, weil es in keiner der vorhandenen Antworten enthalten war, und für Knoten ist es die beste Antwort. Es ist einfach, die Dokumente dafür in github.com/nodejs/node
amara
6

Aktualisiert: 2019

Bluebird.js bietet dies an: http://bluebirdjs.com/docs/api/isfulfilled.html

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

Wenn Sie lieber Ihren eigenen Wrapper erstellen möchten, finden Sie hier einen schönen Blog darüber.

Da JavaScript Single-Threaded ist, ist es schwierig, einen ausreichend häufigen Anwendungsfall zu finden, um dies in die Spezifikation aufzunehmen. Der beste Ort, um zu wissen, ob ein Versprechen gelöst wird, ist .then (). Wenn Sie testen, ob ein Versprechen erfüllt ist, wird eine Abfrageschleife erstellt, die höchstwahrscheinlich in die falsche Richtung weist.

async / await ist ein schönes Konstrukt, wenn Sie asynchronen Code synchron begründen möchten.

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

Ein weiterer nützlicher Aufruf ist 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"]

Als ich zum ersten Mal nach dieser Antwort griff, war dies der Anwendungsfall, nach dem ich gesucht hatte.

Michael Cole
quelle
5

Auf diese Weise können Sie Ihre Versprechen einpacken

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;
}
Spinnen Schwein
quelle
5
Dies würde erfordern, dass OP in einer vorherigen Runde der Ereignisschleife Zugriff auf das Versprechen erhält . Da .thenimmer asynchron ausgeführt wird, wird OP, der ein Versprechen in derselben Runde prüfen möchte, hier nicht das richtige Ergebnis erhalten. Hinweis OP fragte speziell nach synchroner Inspektion und erwähnte, dass sie bereits über asynchrone Inspektion Bescheid wissen.
Benjamin Gruenbaum
@BenjaminGruenbaum: Würden die Standardwerte nicht angezeigt, wenn der Code in derselben "Kurve" ihn aufruft?
Dandavis
Natürlich müssten Sie alle Ihre Versprechen zur Erstellungszeit einpacken. zB innerhalb der Funktionen, die sie erstellen und zurückgeben.
SpiderPig
3
Richtig, zu diesem Zeitpunkt sind sie keine nativen Versprechen mehr. Sie können sie auch so erweitern, wie sie mit Unterklassen erweitert werden sollen, sodass Sie dies elegant tun können, anstatt Affen-Patching-Eigenschaften für ein Objekt.
Benjamin Gruenbaum
Unabhängig davon, ob Sie ein Versprechen wie von mir gezeigt oder durch Unterklassifizierung verlängern, müssten Sie in jedem Fall immer noch Ihre eigene Version von then hinzufügen und fangen.
SpiderPig
5

Es ist in der Tat ziemlich ärgerlich, dass diese grundlegende Funktionalität fehlt. Wenn Sie node.js verwenden, sind mir zwei Problemumgehungen bekannt, von denen keine sehr hübsch ist. Beide folgenden Snippets implementieren dieselbe 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' }

Es scheint keine Möglichkeit zu geben, die letzten beiden Versprechenszustände mit beiden Tricks zu unterscheiden.

1. Verwenden Sie die V8-Debug-API

Dies ist der gleiche Trick, der util.inspectverwendet wird.

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. Führen Sie Mikrotasks synchron aus

Dies vermeidet die Debug-API, hat jedoch eine erschreckende Semantik, da alle ausstehenden Mikrotasks und process.nextTickRückrufe synchron ausgeführt werden. Es hat auch den Nebeneffekt, dass verhindert wird, dass der Fehler "Ablehnung eines nicht behandelten Versprechens" jemals für das geprüfte Versprechen ausgelöst wird.

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 };
};
Matthijs
quelle
Es ist sehr unsicher process._tickCallback(oder sogar% RunMicrotick) - es wird zufällig Dinge in Ihrem Code brechen. Ich habe verzweifelt versucht, es zum Laufen zu bringen (meistens für gefälschte Timer in asynchronen Funktionen), und es war von der Knotenseite aus nie stabil genug. Ich habe es irgendwie aufgegeben, daran zu arbeiten. Die V8-Debugspiegel-API ist hier völlig angemessen.
Benjamin Gruenbaum
Und .. DeprecationWarning: DebugContext has been deprecated and will be removed in a future version.:( Sieht aus wie V8 es entfernt hat
Benjamin Gruenbaum
Wir (Knoten) können völlig fragen V8 für eine API oder eine API belichten bei einem Versprechen suchen Zustand direkt obwohl - wenn Sie ein Problem bei öffnen github.com/nodejs/promise-use-cases ich es gerne mit V8 bringen
Benjamin Gruenbaum
1
Ein Kommentar weiter unten in diesem Thema ergab, dass eine API bereits vorhanden zu sein scheint: process.binding('util').getPromiseDetails( promise )Rückgabe [ 0, ]für ausstehend, [ 1, value ]erfüllt und [ 2, value ]abgelehnt.
Matthijs
3

Vorsichtsmaßnahme: Diese Methode verwendet undokumentierte Node.js-Interna und kann ohne Vorwarnung geändert werden.

In Node können Sie mithilfe von den Status eines Versprechens synchron bestimmen process.binding('util').getPromiseDetails(/* promise */);.

Dies wird zurückkehren:

[0, ] für anhängige,

[1, /* value */] für erfüllt, oder

[2, /* value */] für abgelehnt.

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']

Packen Sie dies in eine Hilfsfunktion:

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

getStatus(pending); // pending
getStatus(fulfilled); // fulfilled
getStatus(rejected); // rejected
Scott Rüdiger
quelle
Scheint nicht von innen heraus zu funktionieren jest(das ist der einzige Ort, an dem ich wirklich interessiert bin). Die Funktion existiert, scheint aber immer zurückzukehren undefined. Wie finde ich heraus, was los ist?
Adam Barnes
Hmm, ich erinnere mich, dass es in mir funktioniert hat mocha. Ich habe es jestaber nie versucht . Starten Sie möglicherweise eine neue Frage, die hier verlinkt ist, und geben Sie sowohl Ihre Node.js-Version als auch Ihre jestVersion an.
Scott Rüdiger
Leider interessiert mich das nicht mehr sehr. Ich habe im Grunde versucht, meine manuell auflösbare / ablehnbare Funktion Promisezu testen, die ich nur zum Testen von Dingen verwendet habe, die ausgeführt werden sollten, während a Promiseansteht, aber ich dachte mir, solange das, was ich geschrieben habe, funktioniert, besteht keine Notwendigkeit, dies zu testen zusätzlich zu dem, was darauf beruht.
Adam Barnes
2

Sie können eine Variable verwenden, um den Status zu speichern, den Status manuell auf diese Variable festzulegen und diese Variable zu überprüfen.

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

Dies bedeutet natürlich, dass Sie Zugriff auf den Originalcode des Versprechens haben müssen. Wenn Sie dies nicht tun, können Sie Folgendes tun:

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

Meine Lösung ist mehr Codierung, aber ich denke, Sie müssten dies wahrscheinlich nicht für jedes Versprechen tun, das Sie verwenden.

Großer Name
quelle
2

Ab Node.js Version 8 können Sie jetzt das Wise-Inspection- Paket verwenden, um native Versprechen synchron zu überprüfen (ohne gefährliche Hacks).

Joshua Wise
quelle
2

Sie können Promise.prototype eine Methode hinzufügen. Es sieht aus wie das:

Bearbeitet: Die erste Lösung funktioniert nicht richtig, wie die meisten Antworten hier. Es wird "ausstehend" zurückgegeben, bis die asynchrone Funktion ".then" aufgerufen wird, was nicht sofort geschieht. (Gleiches gilt für Lösungen mit Promise.race). Meine zweite Lösung löst dieses 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;
    };
}

Sie können es für jedes Versprechen verwenden. Zum Beispiel:

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

Zweite (und richtige) Lösung:

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

Und benutze es:

Hinweis : In dieser Lösung müssen Sie den Operator "new" nicht verwenden.

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

Hier ist eine ausführlichere es6-Version von QueryablePromise, die es ermöglicht, nach der ersten Auflösung zu verketten und zu fangen und die API sofort aufzulösen oder abzulehnen, um die API mit dem nativen Versprechen in Einklang zu halten.

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>

synthet1c
quelle
1

awaitVerwendung der Antwort von @ jib mit idiomatischem Prototyping.

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"
})();

Beachten Sie, dass diese asynchrone Funktion "fast" sofort wie eine synchronisierte Funktion ausgeführt wird (oder tatsächlich möglicherweise sofort ausgeführt wird).

Valen
quelle
1

2019:

Wie ich weiß, ist der einfache Weg, dies zu tun thenable, ein superdünner Wrapper um Versprechen oder einen asynchronen Job.

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
})
pery mimon
quelle
1

Mit extendder Promise-Klasse können Sie eine neue abfragbare Promise-Klasse erstellen .

Sie können Ihre eigene Unterklasse erstellen QueryablePromise, indem Sie beispielsweise von der nativ verfügbaren PromiseKlasse erben , für deren Instanzen eine statusEigenschaft verfügbar ist, mit der Sie den Status der Versprechenobjekte synchron abfragen können . Eine Implementierung kann unter oder verweisen zu sehen dies für eine bessere Erklärung.

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)

UtkarshPramodGupta
quelle
Leider gibt keine vorhandene API diese neue Klasse zurück. Wie stellst du dir vor, dass die Leute das benutzen?
Fock
@jib Danke für deine Antwort. Was meinst du damit, dass keine API diese Klasse zurückgeben würde? :(
UtkarshPramodGupta
Keine vorhandenen APIs geben es zurück, da sie geschrieben werden müssten, um es zurückzugeben, oder? ZB wenn ich es nenne fetch, wird es ein einheimisches Versprechen zurückgeben. Wie würde Ihre Klasse dabei helfen?
Fock
Können wir diesen Abruf nicht einfach wie folgt in unser neues QuerablePromise einbinden : const queryableFetch = new QueryablePromise((resolve, reject) => {fetch(/.../).then((data) => resolve(data)) })? Oder gibt es ein Problem damit? : /
UtkarshPramodGupta
Das sollte funktionieren, vergessen Sie es einfach nicht , err => reject(err)als zweites Argument , sonst thenwerden Fehler nicht korrekt weitergegeben (unter anderem , weil es als Anti-Pattern des Versprechenskonstruktors gilt ). Es ist zwar nicht wirklich synchron (z. B. wird ein bereits gelöstes Versprechen nicht erkannt), aber möglicherweise nützlich in Fällen, in denen Sie den Anrufer nicht kontrollieren und die Antwort sofort benötigt wird.
Fock
1

Es gibt noch eine andere elegante und hackige Methode, um zu überprüfen, ob ein Versprechen noch aussteht, indem Sie das gesamte Objekt in einen String konvertieren und es mithilfe von inspect wie folgt überprüfen : util.inspect(myPromise).includes("pending").

Getestet auf Node.js 8,9,10,11,12,13

Hier ist ein vollständiges Beispiel

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)
})()

Ergebnis:

true
true
false
false
false
devio
quelle
0

Wenn Sie ES7 Experimental verwenden, können Sie Async verwenden, um das Versprechen, das Sie hören möchten, einfach zu verpacken.

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;
}
Ezequiel S. Pereira
quelle
0

Ich habe ein kleines npm-Paket mit dem Versprechen-Wert geschrieben, das einen Versprechen-Wrapper mit einer resolvedFlagge versehen hat:

https://www.npmjs.com/package/promise-value

Es gibt auch synchronen Zugriff auf den Versprechungswert (oder Fehler). Dies ändert nicht das Promise-Objekt selbst, sondern folgt dem Wrap, anstatt das Muster zu erweitern.

Daniel Winterstein
quelle
0

Dies ist eine ältere Frage, aber ich habe versucht, etwas Ähnliches zu tun. Ich muss n Arbeiter am Laufen halten. Sie sind in einem Versprechen strukturiert. Ich muss scannen und sehen, ob sie gelöst, abgelehnt oder noch ausstehend sind. Wenn es behoben ist, brauche ich den Wert. Wenn es abgelehnt wird, kann das Problem behoben werden oder steht noch aus. Wenn gelöst oder abgelehnt, muss ich eine andere Aufgabe starten, um weiterzumachen. Ich kann mit Promise.all oder Promise.race keine Möglichkeit finden, dies zu tun, da ich weiterhin Versprechen in einem Array arbeite und keine Möglichkeit finde, sie zu löschen. Also erstelle ich einen Arbeiter, der den Trick macht

Ich benötige eine Versprechen-Generator-Funktion, die ein Versprechen zurückgibt, das bei Bedarf aufgelöst oder abgelehnt wird. Es wird von einer Funktion aufgerufen, die das Framework einrichtet, um zu wissen, was das Versprechen tut.

Im folgenden Code gibt der Generator einfach ein Versprechen zurück, das auf setTimeout basiert.

Hier ist es

//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 gibt ein Objekt zurück, das das Versprechen sowie dessen Status und Rückgabewert enthält.

Der folgende Code führt eine Schleife aus, die den Status testet und neue Worker erstellt, um ihn bei 3 laufenden Workern zu halten.

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();

Getestet in node.js

Übrigens Nicht in dieser Antwort, sondern in anderen zu ähnlichen Themen, ich hasse es, wenn jemand sagt "Sie verstehen nicht" oder "So funktioniert es nicht". Ich gehe im Allgemeinen davon aus, dass der Fragesteller weiß, was er will. Einen besseren Weg vorzuschlagen ist großartig. Eine geduldige Erklärung, wie Versprechen funktionieren, wäre auch gut.

Charles Bisbee
quelle
-1

Ich fand diese Lösung einfach und erlaubte mir, weiterhin native Versprechen zu verwenden, aber nützliche synchrone Prüfungen hinzuzufügen. Ich musste auch nicht eine ganze Versprechensbibliothek einholen.

CAVEAT: Dies funktioniert nur, wenn der aktuelle Ausführungsthread unterbrochen ist, damit die Versprechen ausgeführt werden können, bevor die synchronen Konstrukte überprüft werden. Das macht dies von geringerer Nützlichkeit, als ich ursprünglich gedacht hatte - dennoch nützlich für meinen Anwendungsfall (Danke Benjamin Gruenbaum für den Hinweis)

/**
 * 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);

Von https://ourcodeworld.com/articles/read/317/how-to-check-if-a-javascript-promise-has-been-fulfilled-rejected-or-resolved , basierend auf ihrer Antwort auf Gibt es einen Weg zu Sagen Sie, ob ein ES6-Versprechen erfüllt / abgelehnt / gelöst wurde?

Akrikos
quelle
Wie in Ihrem Kommentar zu meiner Antwort hinzugefügt - das ist völlig falsch: Damit können Sie den Status eines Versprechens nicht synchron überprüfen - Zum Beispiel MakeQueryablePromise(Promise.resolve(3)).isResolvedist es falsch, aber das Versprechen ist ganz offensichtlich gelöst. Ganz zu schweigen davon, dass in dieser Antwort auch der Begriff "gelöst" und "erfüllt" falsch verwendet wird. Um dies zu erreichen, können Sie einfach .thenselbst einen Handler hinzufügen - was den Punkt der synchronen Inspektion völlig verfehlt.
Benjamin Gruenbaum
Ich verstehe, was Sie sagen, und Sie machen einen guten Punkt. Die Single-Thread-Natur von JS steht im Weg, nicht wahr? Sie müssen die aktuelle Ausführung unterbrechen, damit das Versprechen als gelöst markiert wird. let wrappedPromise = MakeQueryablePromise(Promise.resolve(3)); setTimeout(function() {console.log(wrappedPromise.isFulfilled())}, 1);Was, solange Sie das tun, funktioniert das gut. Aber Sie müssen diese Tatsache verstehen, damit dies nützlich ist. Ich werde die Beschreibung mit dieser Einschränkung aktualisieren. Ich stimme auch zu, dass die Funktionsbenennung besser / idiomatischer sein könnte.
Akrikos
Aber an diesem Punkt könnten Sie nur thendas ursprüngliche Versprechen halten und dasselbe erreichen, da es sowieso asynchron ist. Es gibt einen Weg process.binding('util').getPromiseDetails, der zu funktionieren scheint, aber es wird eine private API verwendet
Benjamin Gruenbaum
Es ist unerträglich, dann die ganze Zeit zu müssen und macht den Code viel schwieriger zu verstehen. Vor allem, wenn es mir nur darum geht, ob das Versprechen abgelehnt wurde oder nicht - ich kann diesen Status entweder woanders speichern oder so etwas tun. Ich gebe zu, dass ich die anderen Lösungen hier nicht gründlich gelesen habe, bevor ich meine eigenen veröffentlicht habe - entschuldige mich dafür. Dieses Problem ist klebriger als ich zuerst gedacht hatte.
Akrikos