wake-up-neo.com

Wie kann ich den Status eines JavaScript-Promises synchron ermitteln?

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 'erledigt'
  • "erledigt" und "abgelehnt"
  • 'steht aus'

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

  • ist das Versprechen erfüllt?

  • wenn ja, ist das Versprechen gelöst?

Ich weiß, dass ich mit #then() die asynchrone Ausführung von Arbeiten planen kann, nachdem sich der Status von Promise geändert hat. Ich frage NICHT, wie das geht.

Diese Frage bezieht sich speziell auf synchrone Abfrage des Status eines Versprechens . Wie kann ich das erreichen?

94
jokeyrhyme

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

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 core schreiben), können Sie bestimmte Tools verwenden (wie private Symbole), um dies zu erreichen. Das ist zwar superspezifisch und nicht im Userland.

52

 enter image description here

Promise-Status-Async macht den Trick. Es ist asynchron, verwendet jedoch nicht then, um zu warten, dass das Versprechen gelöst wird.

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;
}
30
0xaB

Du kannst mit Promise.resolve ein Rennen machen
Es ist nicht synchron, aber 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 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) (kommentieren Sie die 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

promiseState make .race und .then: Level 2

15
Steween

Nein, keine Sync-API, aber hier ist meine Version von async 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

14
jib

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
}
6
rabbitco

Es ist in der Tat ziemlich ärgerlich, dass diese grundlegende Funktionalität fehlt. Wenn Sie node.js verwenden, sind mir zwei Problemumgehungen bekannt, und beide sind nicht sehr hübsch. Die beiden folgenden Ausschnitte 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 Versprechungszustände mit einem der beiden Tricks zu unterscheiden.

1. Verwenden Sie die V8-Debug-API

Dies ist der gleiche Trick, den util.inspect verwendet.

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 synchron Mikrotasks aus

Dies vermeidet die Debug-API, hat jedoch einige beängstigende Semantik, da alle ausstehenden Mikrotasks und process.nextTick-Callbacks synchron ausgeführt werden. Es hat auch den Nebeneffekt, dass verhindert wird, dass der Fehler "unbehandeltes Versprechen zur Ablehnung" für das geprüfte Versprechen jemals 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 };
};
4
Matthijs

Sie können Ihre Versprechen auf diese Weise 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;
}
4
SpiderPig

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

3
Joshua Wise

Achtung: Diese Methode verwendet undokumentierte Node.js-Interna und kann ohne Warnung geändert werden.

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

Dies wird zurückkehren:

[0, ] für ausstehende,

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

Dies in eine Hilfsfunktion einwickeln:

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

getStatus(pending); // pending
getStatus(fulfilled); // fulfilled
getStatus(rejected); // rejected
3
Scott Rudiger

im Knoten sagen Sie process.binding('util').getPromiseDetails(promise)

2
amara

sie können den Status mit einer Variablen speichern, den Status manuell auf diese Variable setzen und diese Variable ü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.

2
BigName

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

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

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. (Das gleiche gilt für Lösungen, die Promise.race verwenden). 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 korrekte) 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: 

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

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

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

Ich brauche eine Versprechengeneratorfunktion, die ein Versprechen zurückgibt, das je nach Bedarf auflöst oder ablehnt. Es wird von einer Funktion aufgerufen, die den Rahmen aufbaut, um zu wissen, was das Versprechen macht.

Im folgenden Code gibt der Generator einfach ein Versprechen aus, 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 den Status und den zurückgegebenen Wert enthält.

Der folgende Code führt eine Schleife aus, die den Status testet und neue Worker erstellt, um den Status von drei Workern beizubehalten.

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. 

0
Charles Bisbee

Wenn Sie mit ES7 experimentell arbeiten, können Sie async verwenden, um das Versprechen, das Sie hören möchten, einfach einzuwickeln.

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

await Verwendung zu @ jibs Antwort , 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:
(async () => {
    console.log(await <Your Promise>.state);
    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 möglicherweise sogar sofort ausgeführt wird).

0
Valen

Ich habe ein kleines npm-Paket mit dem Namen promise-value geschrieben, das einen Versprechen-Wrapper mit einem resolved-Flag enthält:

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

Es gibt auch einen synchronen Zugriff auf den Versprechungswert (oder Fehler). Das Promise-Objekt selbst wird dadurch nicht verändert, es folgt dem Wrap, anstatt das Muster zu erweitern.

0

Hier ist eine ausführlichere es6-Version von QueryablePromise, die es der Kette ermöglicht, nach der ersten Lösung zu verketten und zu fangen und sofort aufzulösen oder abzulehnen, um die API im Einklang mit dem nativen Promise 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>

0
synthet1c