Come posso determinare in modo sincrono lo stato di una promessa JavaScript?


149

Ho una pura promessa JavaScript (implementazione integrata o poly-fill):

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

Dalla specifica , una Promessa può essere una delle seguenti:

  • 'risolto' e 'risolto'
  • "risolto" e "rifiutato"
  • 'in sospeso'

Ho un caso d'uso in cui desidero interrogare la Promessa in modo sincrono e determinare:

  • la promessa è stabilita?

  • in tal caso, la Promessa è stata risolta?

So che posso utilizzare #then()per pianificare il lavoro da eseguire in modo asincrono dopo lo stato di modifica Promessa. NON sto chiedendo come farlo.

Questa domanda riguarda specificamente l' interrogazione sincrona dello stato di una Promessa . Come posso raggiungere questo obiettivo?


6
imposta una proprietà sulla promessa che può essere vista dall'esterno e usa then () per cambiare la proprietà.
dandavis,

@jokeyrhyme fwiw, codice sorgente v8.google.com/p/v8/source/browse/branches/bleeding_edge/src/… vedi var promiseStatus = NEW_PRIVATE("Promise#status");, PromiseSetfunzione aSET_PRIVATE(promise, promiseStatus, status);
guest271314


Sembra strano che se lo fai const a = Promise.resolve ('baz'); console.log (a); e guarda nella console di Chrome, vedi Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "baz"} proto: Promise [[PromiseStatus]]: "resolved" [[PromiseValue]]: "baz "e la gente afferma che non si può fare. Come sta andando Chrome? (lo stava facendo in un Plunker con Angular plnkr.co/edit/IPIWgLJKQStI5ubXmcsF
JGFMK

L'uso del nodo v11.12.0 console.log mostrerà lo stato della promessa. EG console.log(Promise.new((resolve, reject) => {})=>Promise { <pending> }
Puhlze

Risposte:


77

Non esiste alcuna API di ispezione sincrona per le promesse JavaScript native. È impossibile farlo con promesse native. La specifica non specifica tale metodo.

Le librerie di Userland possono farlo, e se stai prendendo di mira un motore specifico (come v8) e hai accesso al codice della piattaforma (cioè puoi scrivere codice nel core ), puoi usare strumenti specifici (come simboli privati) per raggiungere questo obiettivo . È super specifico però e non in userland.


4
Nota: credo sinceramente che i casi d'uso per l'ispezione sincrona siano pochi e molto rari, se condividi il tuo caso d'uso concreto in una nuova domanda chiedendoti come raggiungerlo senza ispezione sincrona - ti darò una risposta se qualcuno non lo farà
picchiami

4
Anche se i casi d'uso sono rari, che danno farebbe qualcosa del genere? Avrei bisogno di un controllo dello stato come questo per vedere se il lavoro precedente era terminato e se posso richiedere un altro lavoro. E non posso semplicemente impostare una variabile esterna perché l'oggetto ha il potenziale per cambiare proprietario senza preavviso. Cosa c'è di più irritante è che VEDO Node.js ha accesso a queste informazioni perché me lo mostrano quando lo ispeziono, ma non c'è modo di accedervi oltre all'analisi delle stringhe ??
Tustin2121,

9
Quindi dobbiamo buttare via le promesse native perché sono poco pratiche e usano sempre l'uccellino azzurro. Grandi notizie! Come propongo promesse native di diventare deprecate e buttate fuori dal motore del nodo?
user619271

1
Molte cose, .anyinvece , avremmo dovuto fare delle ipotesi e fare un errore perché Mark ha insistito. Per uno, Promise.race([])è una promessa in sospeso per sempre (e non un errore), in genere si desidera la prima promessa di successo e non solo la prima promessa. Comunque, questo non è veramente rilevante per la domanda posta - OP ha chiesto l'ispezione sincrona e non circa .racee le sue numerose carenze.
Benjamin Gruenbaum,

5
@Akrikos che risponde non ti consente di ispezionare in modo sincrono lo stato di una promessa - Ad esempio MakeQueryablePromise(Promise.resolve(3)).isResolvedè falso ma la promessa è ovviamente risolta. Per non parlare del fatto che la risposta utilizza anche il termine "risolto" e "soddisfatto" in modo errato. Per fare questa risposta, potresti semplicemente aggiungere un .thengestore da solo, che manca completamente al punto di ispezione sincrona.
Benjamin Gruenbaum,

31

inserisci qui la descrizione dell'immagine

promise-status-async fa il trucco. È asincrono ma non serve thenper attendere che la promessa venga risolta.

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 ha chiesto come farlo in modo sincrono però
Klesun,

28

No, nessuna API di sincronizzazione, ma ecco la mia versione dell'asincrono promiseState(con l'aiuto di @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


C'è un ragionamento specifico dietro questa costruzione? Mi sembra inutilmente complicato. Per quanto posso dire questo funziona in modo identico: anche Promise.race([ Promise.resolve(p).then(() => "fulfilled", () => "rejected"), Promise.resolve().then(() => "pending") ]); se questo mi sembra più sicuro: const t = {}; return Promise.race([p,t]).then(v => v === t ? "pending" : "fulfilled", () => "rejected") ed evita di creare ulteriori promesse che persistono fintanto che l'originale p è in sospeso.
Matthijs,

Grazie @Matthijs! Ho semplificato la mia risposta.
fiocco

16

Puoi fare una gara con Promise.resolve
Non è sincrono ma succede adesso

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

Un piccolo script per testare e capire il loro significato in modo asincrono

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

risultati con ritardo (0) (commenta con un certo ritardo)

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

e i risultati di questo test con Firefox (Chrome mantiene l'ordine)

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 crea .race e .then: Livello 2


3
Invece di 'a value that p should not return'usare un simbolo
programmatore

1
@ programmer5000 Qual è il vantaggio?
Moritz Schmitz contro Hülst,

2
@ MoritzSchmitzv.Hülst a Symbolsarebbe un valore unico, quindi non dovresti mai indovinare quale "valore [...] p non dovrebbe restituire". Tuttavia, un riferimento a un oggetto specifico funzionerebbe altrettanto bene.
Scott Rudiger,

7

Puoi usare un (brutto) hack in Node.js fino a quando non viene offerto un metodo nativo:

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
L'ho ridotto a un polyfill:Promise.prototype.isPending = function(){ return util.inspect(this).indexOf("<pending>")>-1; }
Tustin2121,

5
È orrendo .
John Weisz,

@JohnWeisz La cosa orrenda è la mancanza di compatibilità con le versioni precedenti. Sto cercando di integrare un'API promettente in una base di codice che presuppone che tutto sia sincrono. O sta facendo qualcosa di orrendo o riscrivendo enormi blocchi di codice. Ad ogni modo sto commettendo un'atrocità.
rath

4
basta usareprocess.binding('util').getPromiseDetails
amara,

@ Tustin2121 Per alcune versioni fallirà con qualcosa del genere Promise.resolve('<pending>').
user202729

7

nel nodo, dire interno non documentato 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> ]

l'ho aggiunto perché non si trovava in nessuna delle risposte esistenti e per nodo è la risposta migliore. è facile cercare i documenti per esso in github.com/nodejs/node
amara,

6

Aggiornato: 2019

Bluebird.js offre questo: http://bluebirdjs.com/docs/api/isfulfilled.html

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

Se preferisci creare il tuo wrapper, ecco un bel blog a riguardo.

Poiché JavaScript è a thread singolo, è difficile trovare un caso d'uso abbastanza comune per giustificare l'inserimento di questo nella specifica. Il posto migliore per sapere se una promessa è stata risolta è in .then (). Testare se una Promessa è stata riempita creerebbe un ciclo di polling che probabilmente è la direzione sbagliata.

async / await è un bel costrutto se vuoi ragionare in modo sincrono codice asincrono.

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

Un'altra utile chiamata è 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"]

Quando ho raggiunto per la prima volta questa risposta, questo è il caso d'uso che stavo cercando.


5

Puoi racchiudere le tue promesse in questo modo

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
Ciò richiederebbe OP per ottenere l'accesso alla promessa in un turno precedente del ciclo degli eventi . Poiché .thenesegue sempre OP in modo asincrono, chi desidera ispezionare una promessa nello stesso turno non otterrà il risultato corretto qui. Nota OP ha chiesto specificamente l' ispezione sincrona e ha affermato di conoscere già l'ispezione asincrona.
Benjamin Gruenbaum,

@BenjaminGruenbaum: i valori predefiniti non verrebbero se il codice nella stessa "svolta" lo chiamasse?
dandavis,

Ovviamente dovresti concludere tutte le tue promesse al momento della creazione. ad esempio all'interno delle funzioni che le creano e le restituiscono.
SpiderPig

3
Bene, a quel punto non sono più promesse native, potresti anche estenderle nel modo in cui dovrebbero essere estese con la sottoclasse che ti permetterebbe di fare questo elegantemente anziché le proprietà di patch delle scimmie su un oggetto.
Benjamin Gruenbaum,

Sia che estenda una promessa nel modo in cui ho mostrato o in una sottoclasse, in ogni caso dovresti comunque aggiungere la tua versione di allora e catturare.
SpiderPig

5

È davvero abbastanza fastidioso che manchi questa funzionalità di base. Se stai usando node.js, allora conosco due soluzioni alternative, nessuna delle quali è molto carina. Entrambi i frammenti di seguito implementano la stessa 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' }

Non sembra esserci alcun modo per distinguere gli ultimi due stati della promessa usando entrambi i trucchi.

1. Utilizzare l'API di debug V8

Questo è lo stesso trucco che util.inspectusa.

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. Eseguire microtask in modo sincrono

Questo evita l'API di debug, ma ha una semantica spaventosa, facendo sì che tutti i microtask e process.nextTickcallback in sospeso vengano eseguiti in modo sincrono. Ha anche l'effetto collaterale di impedire che l'errore di "rifiuto di promessa non gestita" venga mai attivato per la promessa ispezionata.

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

È molto pericoloso process._tickCallback(o addirittura% RunMicrotick) - romperà casualmente le cose nel tuo codice. Ho provato disperatamente a farlo funzionare (per i falsi timer nelle funzioni asincrone, per lo più) e non era mai abbastanza stabile dal lato Nodo. Ho smesso di lavorarci su. L'API mirror di debug V8 è del tutto appropriata qui.
Benjamin Gruenbaum,

E .. DeprecationWarning: DebugContext has been deprecated and will be removed in a future version.:( Sembra che V8 l'abbia rimosso
Benjamin Gruenbaum il

Noi (Nodo) possiamo chiedere totalmente a V8 un'API o esporre un'API per guardare direttamente lo stato di una promessa - se apri un problema su github.com/nodejs/promise-use-cases lo farò felicemente con V8
Benjamin Gruenbaum,

1
Un commento più in basso in questo argomento ha rivelato che un'API sembra già esistere: process.binding('util').getPromiseDetails( promise )resi [ 0, ]per in sospeso, [ 1, value ]per adempiuti e [ 2, value ]per respinti.
Matthijs,

3

Avvertenza: questo metodo utilizza interni Node.js non documentati e potrebbe essere modificato senza preavviso.

In Nodo puoi determinare in modo sincrono lo stato di una promessa usando process.binding('util').getPromiseDetails(/* promise */);.

Questo restituirà:

[0, ] in attesa,

[1, /* value */] per adempiuto, o

[2, /* value */] per respinto.

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

Avvolgendolo in una funzione di supporto:

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

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

Non sembra funzionare dall'interno jest(che è l'unico posto a cui sono interessato, davvero). La funzione esiste, ma sembra sempre di tornare undefined. Come faccio a sapere cosa c'è che non va?
Adam Barnes,

Hmm, ricordo che funzionava all'interno mocha; mai provato con jestperò. Forse iniziare una nuova domanda che collega qui e includere la versione di Node.js e la jestversione?
Scott Rudiger,

Sfortunatamente non sono più interessato a molto. Stavo fondamentalmente cercando di testare la sanità mentale sul mio risolvibile / riutilizzabile manualmente Promiseche stavo usando solo per testare cose che dovrebbero essere in corso mentre Promiseè in sospeso, ma ho pensato che fintanto che ciò che ho scritto funziona, quindi non c'è bisogno di testarlo oltre a ciò che si basa su di esso.
Adam Barnes,

2

quello che puoi fare è usare una variabile per memorizzare lo stato, impostare manualmente lo stato su quella variabile e controllare quella variabile.

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

ovviamente, questo significa che devi avere accesso al codice originale della promessa. In caso contrario, puoi fare:

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

La mia soluzione è più codifica, ma penso che probabilmente non dovresti farlo per ogni promessa che usi.



2

È possibile aggiungere un metodo a Promise.prototype. Sembra così:

Modificato: la prima soluzione non funziona correttamente, come la maggior parte delle risposte qui. Restituisce "in sospeso" fino a quando non viene invocata la funzione asincrona ".then", che non avviene immediatamente. (Lo stesso vale per le soluzioni che utilizzano Promise.race). La mia seconda soluzione risolve questo problema.

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

Puoi usarlo su qualsiasi promessa. Per esempio:

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

Seconda (e corretta) soluzione:

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

E usalo:

Avviso : in questa soluzione non è necessario utilizzare il "nuovo" operatore.

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

1

Ecco una versione es6 più raffinata di QueryablePromise, che consente la possibilità di incatenarsi quindi e catturare dopo la prima risoluzione e di risolvere immediatamente o rifiutare per mantenere l'API coerente con la Promessa nativa.

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

awaitutilizzo alla risposta di @ jib , con prototipazione idiomatica.

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

si noti che questa funzione asincrona esegue "quasi" immediatamente come la funzione sincronizzata (o effettivamente può essere istantaneamente).


1

2019:

Il modo semplice per farlo, come so thenable, è un involucro super sottile che promette o qualsiasi lavoro asincrono.

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

È possibile extendla classe Promise per creare una nuova classe Promise interrogabile .

Puoi creare la tua sottoclasse, per esempio QueryablePromise, ereditando dalla Promiseclasse nativamente disponibile , le cui istanze avrebbero una statusproprietà disponibile su di essa che puoi usare per interrogare lo stato degli oggetti promessa in modo sincrono . Un'implementazione di esso può essere visto sotto o questo per una migliore spiegazione.

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)


Sfortunatamente, nessuna API esistente restituirà questa nuova classe. Come immagini che la gente lo usi?
fiocco

@jib Grazie per la tua risposta. Cosa vuoi dire che nessuna API restituirebbe questa classe? :(
UtkarshPramodGupta il

Nessuna API esistente lo restituirà, perché dovrebbero essere scritti per restituirlo, giusto? Ad esempio, se lo chiamo fetchrestituirà una promessa nativa. In che modo la tua classe potrebbe aiutarti?
fiocco

Beh, non possiamo semplicemente avvolgere che recuperano gli chiamata nella nostra nuova QuerablePromise come: const queryableFetch = new QueryablePromise((resolve, reject) => {fetch(/.../).then((data) => resolve(data)) })? Oppure, c'è un problema con quello? : /
UtkarshPramodGupta il

Dovrebbe funzionare, ma non dimenticare , err => reject(err)come secondo argomento theno non propagherà correttamente gli errori (tra i motivi per cui è considerato l' anti-modello del costruttore della promessa ). Tuttavia, non è veramente sincrono (ad es. Non rileverà una promessa già risolta), ma forse utile nei casi in cui non si controlla il chiamante e la risposta è necessaria immediatamente.
fiocco

1

Esiste un altro modo elegante e confuso di verificare se una promessa è ancora in sospeso semplicemente convertendo l'intero oggetto in stringa e controllandolo con l'aiuto di ispezionare in questo modo:util.inspect(myPromise).includes("pending") .

Testato su Node.js 8,9,10,11,12,13

Ecco un esempio completo

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

Risultato:

true
true
false
false
false

0

Se stai usando ES7 sperimentale puoi usare asincrono per avvolgere facilmente la promessa che vuoi ascoltare.

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

Ho scritto un piccolo pacchetto npm, valore-promessa, che fornisce un wrapper promessa con una resolvedbandiera:

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

Fornisce inoltre l'accesso sincrono al valore (o errore) della promessa. Ciò non altera l'oggetto Promise stesso, seguendo il modello a capo anziché estendere.


0

Questa è una domanda più vecchia, ma stavo cercando di fare qualcosa di simile. Devo far andare avanti n lavoratori. Sono strutturati in una promessa. Devo scansionare e vedere se sono stati risolti, rifiutati o ancora in sospeso. Se risolto, ho bisogno del valore, se rifiutato fare qualcosa per correggere il problema o in sospeso. Se risolto o rifiutato, devo avviare un'altra attività per continuare. Non riesco a immaginare un modo per farlo con Promise.all o Promise.race poiché continuo a mantenere le promesse in un array e non riesco a trovare alcun modo per eliminarle. Quindi creo un lavoratore che fa il trucco

Ho bisogno di una funzione di generatore di promesse che restituisce una promessa che si risolve o rifiuta, se necessario. Viene chiamato da una funzione che imposta il framework per sapere cosa sta facendo la promessa.

Nel codice sottostante il generatore restituisce semplicemente una promessa basata su setTimeout.

Ecco qui

//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 restituisce un oggetto contenente la promessa, il suo stato e il valore restituito.

Il codice seguente esegue un ciclo che verifica lo stato e crea nuovi lavoratori per mantenerlo su 3 lavoratori in esecuzione.

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

Testato in node.js

A proposito non in questa risposta così tanto, ma in altri su argomenti simili, lo odio quando qualcuno dice "non capisci" o "non è così che funziona" In genere suppongo che l'interrogante sappia cosa vogliono. Suggerire un modo migliore è fantastico. Sarebbe anche utile una spiegazione paziente di come funzionano le promesse.


-1

Ho trovato questa soluzione semplice e mi ha permesso di continuare a utilizzare le promesse native ma di aggiungere utili controlli sincroni. Inoltre non ho dovuto inserire un'intera libreria di promesse.

CAVEAT: Funziona solo se c'è una sorta di interruzione nel thread di esecuzione corrente per consentire alle promesse di essere eseguite PRIMA di controllare i costrutti sincroni. Ciò rende questo di utilità più limitata di quanto avessi inizialmente pensato - comunque utile per il mio caso d'uso (Grazie Benjamin Gruenbaum per averlo sottolineato)

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

Da https://ourcodeworld.com/articles/read/317/how-to-check-if-a-javascript-promise-has-been-fulfilled-rejected-or-resolved che ha basato la loro risposta su C'è un modo per dire se una promessa ES6 è stata rispettata / respinta / risolta?


Come aggiunto nel tuo commento sulla mia risposta - questo è del tutto errato: ciò non ti consente di ispezionare in modo sincrono lo stato di una promessa - Ad esempio MakeQueryablePromise(Promise.resolve(3)).isResolvedè falso ma la promessa è ovviamente risolta. Per non parlare del fatto che la risposta utilizza anche il termine "risolto" e "soddisfatto" in modo errato. Per fare questa risposta, potresti semplicemente aggiungere un .thengestore da solo, che manca completamente al punto di ispezione sincrona.
Benjamin Gruenbaum,

Vedo quello che stai dicendo e fai un buon punto. La natura a thread singolo di JS si sta mettendo in mezzo, vero? Devi mettere una pausa nell'esecuzione corrente affinché la promessa sia contrassegnata come risolta. let wrappedPromise = MakeQueryablePromise(Promise.resolve(3)); setTimeout(function() {console.log(wrappedPromise.isFulfilled())}, 1);Che finché lo fai, questo funziona bene. Ma devi capire questo fatto perché questo sia utile. Aggiornerò la descrizione con quell'avvertimento. Concordo anche sul fatto che la denominazione della funzione potrebbe essere migliore / più idiomatica.
Akrikos,

Ma a quel punto potresti solo thenla promessa originale e realizzare la stessa cosa poiché è asincrona comunque. C'è un modo process.binding('util').getPromiseDetailsche sembra funzionare, ma utilizza un'API privata
Benjamin Gruenbaum,

È odioso doverlo fare continuamente e rende il codice molto più difficile da capire. Soprattutto quando tutto ciò che mi interessa è se la promessa è stata respinta o meno - quindi le mie opzioni sono o di conservare quello stato altrove o fare qualcosa del genere. Ammetto di non aver letto attentamente le altre soluzioni qui prima di pubblicare le mie - mi scuso per quello. Questo problema è più evidente di quanto avessi inizialmente pensato.
Akrikos,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.