Comment puis-je déterminer de manière synchrone l'état d'une promesse JavaScript?

149

J'ai une pure promesse JavaScript (implémentation intégrée ou poly-fill):

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

À partir de la spécification , une promesse peut être l'une des suivantes:

  • 'réglé' et 'résolu'
  • 'réglé' et 'rejeté'
  • 'en attente'

J'ai un cas d'utilisation où je souhaite interroger la promesse de manière synchrone et déterminer:

  • la promesse est-elle réglée?

  • si oui, la promesse est-elle résolue?

Je sais que je peux utiliser #then()pour planifier le travail à effectuer de manière asynchrone après le changement d'état de la promesse. Je ne demande PAS comment faire cela.

Cette question concerne spécifiquement l' interrogation synchrone de l'état d'une promesse . Comment puis-je atteindre cet objectif?

jokeyrhyme
la source
6
définissez une propriété sur la promesse qui peut être vue de l'extérieur, et utilisez then () pour changer la propriété.
dandavis
@jokeyrhyme fwiw, v8 source code.google.com/p/v8/source/browse/branches/bleeding_edge/src/… voir var promiseStatus = NEW_PRIVATE("Promise#status");, PromiseSetfonction àSET_PRIVATE(promise, promiseStatus, status);
guest271314
On y va: esdiscuss.org/topic/…
jokeyrhyme
Il semble étrange que si vous faites const a = Promise.resolve ('baz'); console.log (a); et regardez dans la console Chrome, vous voyez Promise {[[PromiseStatus]]: "résolu", [[PromiseValue]]: "baz"} proto: Promise [[PromiseStatus]]: "résolu" [[PromiseValue]]: "baz "et les gens prétendent que cela ne peut pas être fait. Comment fait Chrome? (faisait cela dans un Plunker avec Angular plnkr.co/edit/IPIWgLJKQStI5ubXmcsF
JGFMK
L'utilisation du nœud v11.12.0 console.log affichera l'état de la promesse. EG console.log(Promise.new((resolve, reject) => {})=>Promise { <pending> }
Puhlze

Réponses:

77

Aucune API d'inspection synchrone de ce type n'existe pour les promesses JavaScript natives. Il est impossible de faire cela avec des promesses natives. La spécification ne spécifie pas une telle méthode.

Les bibliothèques Userland peuvent le faire, et si vous ciblez un moteur spécifique (comme la v8) et avez accès au code de la plate-forme (c'est-à-dire que vous pouvez écrire du code dans le noyau ), vous pouvez utiliser des outils spécifiques (comme des symboles privés) pour y parvenir. . C'est super spécifique cependant et pas dans le userland.

Benjamin Gruenbaum
la source
4
Remarque: je crois honnêtement que les cas d'utilisation de l'inspection synchrone sont rares et très rares, si vous partagez votre cas d'utilisation concret dans une nouvelle question demandant comment y parvenir sans inspection synchrone - je vais donner une réponse si quelqu'un ne le fait pas beat me to it :)
Benjamin Gruenbaum
4
Même si les cas d'utilisation sont rares, quel mal y aurait-il d'inclure quelque chose comme ça? J'aurais besoin d'un contrôle de statut comme celui-ci pour voir si le travail précédent était terminé et si je peux demander un autre travail. Et je ne peux pas simplement définir une variable externe car l'objet a le potentiel de changer de propriétaire sans préavis. Ce qui est plus irritant, c'est que je peux VOIR Node.js a accès à ces informations car il me les montre lorsque je les inspecte, mais il n'y a aucun moyen d'y accéder à part l'analyse des chaînes ??
Tustin2121
9
Nous devons donc rejeter les promesses natives car elles ne sont pas pratiques et utiliser toujours bluebird. Bonne nouvelle! Comment proposer des promesses natives pour devenir obsolètes et rejetées du moteur de nœud?
user619271
1
Beaucoup de choses, nous aurions dû spéculer à la .anyplace et faire une erreur parce que Mark a insisté. D'une part, Promise.race([])est une promesse en suspens pour toujours (et non une erreur), vous voulez généralement la première promesse réussie et pas seulement la première promesse. Quoi qu'il en soit, ce n'est pas vraiment pertinent par rapport à la question posée - OP a posé une question sur l'inspection synchrone et non sur .raceses nombreuses lacunes.
Benjamin Gruenbaum
5
@Akrikos cette réponse ne vous permet pas d'inspecter de manière synchrone l'état d'une promesse - Par exemple, elle MakeQueryablePromise(Promise.resolve(3)).isResolvedest fausse mais la promesse est bien évidemment résolue. Sans oublier que la réponse utilise également les termes «résolu» et «rempli» de manière incorrecte. Pour ce faire, vous pouvez simplement ajouter un .thengestionnaire vous-même - ce qui manque complètement le point d'inspection synchrone.
Benjamin Gruenbaum
31

entrez la description de l'image ici

promise-status-async fait l'affaire. Il est asynchrone mais il ne sert pas thenà attendre que la promesse soit résolue.

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
la source
4
OP a demandé comment le faire de manière synchrone
Klesun
28

Non, pas d'API de synchronisation, mais voici ma version de l'async promiseState(avec l'aide de @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

foc
la source
Y a-t-il un raisonnement spécifique derrière cette construction? Cela me semble inutilement compliqué. Pour autant que je sache, cela fonctionne de la même manière: bien Promise.race([ Promise.resolve(p).then(() => "fulfilled", () => "rejected"), Promise.resolve().then(() => "pending") ]); que cela me semble plus sûr: const t = {}; return Promise.race([p,t]).then(v => v === t ? "pending" : "fulfilled", () => "rejected") et évite de créer des promesses supplémentaires qui persistent tant que le p original est en attente.
Matthijs
Merci @Matthijs! J'ai simplifié ma réponse.
jib
16

Vous pouvez faire une course avec Promise.resolve
Ce n'est pas synchrone mais se produit maintenant

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 petit script pour tester et comprendre leur signification de asynchrone

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

résultats avec delay (0) (commenter le while in delay)

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

et les résultats de ce test avec firefox (chrome garde l'ordre)

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 crée .race et .then: Niveau 2

Steween
la source
3
Au lieu de 'a value that p should not return', utilisez un symbole
programmer5000
1
@ programmer5000 Quel est l'avantage?
Moritz Schmitz contre Hülst
2
@ MoritzSchmitzv.Hülst a Symbolserait une valeur unique, vous n'aurez donc jamais à deviner quelle "valeur [...] p ne doit pas retourner". Cependant, une référence à un objet spécifique fonctionnerait tout aussi bien.
Scott Rudiger
7

Vous pouvez utiliser un hack (laid) dans Node.js jusqu'à ce qu'une méthode native soit proposée:

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
}
lapin
la source
3
Je l'ai réduit à un polyfill:Promise.prototype.isPending = function(){ return util.inspect(this).indexOf("<pending>")>-1; }
Tustin2121
5
C'est horrible .
John Weisz
@JohnWeisz Ce qui est horrible, c'est le manque de rétrocompatibilité. J'essaie d'intégrer une API prometteuse à une base de code qui suppose que tout est synchrone. C'est soit faire quelque chose d'horrible, soit réécrire d'énormes morceaux de code. Dans tous les cas, je commets une atrocité.
rath
4
il suffit d'utiliserprocess.binding('util').getPromiseDetails
amara
@ Tustin2121 Pour certaines versions, cela échouera avec quelque chose comme Promise.resolve('<pending>').
user202729 le
7

dans le nœud, disons interne non documenté 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
la source
J'ai ajouté cela parce que ce n'était dans aucune des réponses existantes, et pour le nœud, c'est la meilleure réponse. il est facile de consulter la documentation pour cela dans github.com/nodejs/node
amara
6

Mise à jour: 2019

Bluebird.js propose ceci: http://bluebirdjs.com/docs/api/isfulfilled.html

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

Si vous préférez créer votre propre wrapper, voici un joli blog à ce sujet.

Parce que JavaScript est monothread, il est difficile de trouver un cas d'utilisation assez courant pour justifier de le placer dans la spécification. Le meilleur endroit pour savoir si une promesse est résolue est dans .then (). Tester si une promesse est remplie créerait une boucle d'interrogation qui est probablement la mauvaise direction.

async / await est une construction intéressante si vous souhaitez raisonner le code async de manière synchrone.

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

Un autre appel utile est 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"]

Quand j'ai atteint cette réponse pour la première fois, c'est le cas d'utilisation que je recherchais.

Michael Cole
la source
5

Vous pouvez conclure vos promesses de cette façon

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;
}
SpiderPig
la source
5
Cela nécessiterait qu'OP ait accès à la promesse lors d'un tour précédent de la boucle d'événements . Puisque .thenexécute toujours de manière asynchrone OP qui veut inspecter une promesse dans le même tour n'obtiendra pas le résultat correct ici. Remarque OP a posé des questions spécifiquement sur l' inspection synchrone et a mentionné qu'il connaissait déjà l'inspection asynchrone.
Benjamin Gruenbaum
@BenjaminGruenbaum: les valeurs par défaut n'apparaîtraient-elles pas si le code du même «tour» l'appelait?
dandavis
Bien sûr, vous devrez conclure toutes vos promesses au moment de la création. par exemple à l'intérieur des fonctions qui les créent et les renvoient.
SpiderPig
3
Bon, à quel point ce ne sont plus vraiment des promesses natives, vous pouvez aussi bien les étendre comme elles sont censées être étendues avec des sous-classements, ce qui vous permettrait de le faire avec élégance au lieu de patcher les propriétés de singe sur un objet.
Benjamin Gruenbaum
Que vous étendiez une promesse comme je l'ai montré ou en sous-classant, dans chaque cas, vous devrez toujours ajouter votre propre version de then et catch.
SpiderPig
5

Il est en effet assez ennuyeux que cette fonctionnalité de base manque. Si vous utilisez node.js, je connais deux solutions de contournement, ni l'une ni l'autre très jolie. Les deux extraits ci-dessous implémentent la même 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' }

Il ne semble pas y avoir de moyen de distinguer les deux derniers états de promesse en utilisant l'une ou l'autre astuce.

1. Utilisez l'API de débogage V8

C'est la même astuce qui util.inspectutilise.

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. Microtâches exécutées de manière synchrone

Cela évite l'API de débogage, mais présente une sémantique effrayante en provoquant l' process.nextTickexécution synchrone de toutes les microtâches et rappels en attente . Elle a également pour effet secondaire d'empêcher l'erreur de «rejet de promesse non gérée» d'être déclenchée pour la promesse inspectée.

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
la source
Il est très dangereux de le faire process._tickCallback(ou même de% RunMicrotick) - cela cassera aléatoirement des choses dans votre code. J'ai désespérément essayé de le faire fonctionner (pour les faux minuteurs dans les fonctions asynchrones, principalement) et ce n'était jamais assez stable du côté du nœud. J'ai en quelque sorte renoncé à y travailler. L'API de miroir de débogage V8 est tout à fait appropriée ici.
Benjamin Gruenbaum
Et .. DeprecationWarning: DebugContext has been deprecated and will be removed in a future version.:( On dirait que V8 l'a enlevé
Benjamin Gruenbaum
Nous (Node) pouvons totalement demander à V8 une API ou exposer une API pour examiner directement l'état d'une promesse - si vous ouvrez un problème sur github.com/nodejs/promise-use-cases, je le présenterai volontiers avec V8
Benjamin Gruenbaum
1
Un commentaire plus bas dans cette rubrique a révélé qu'une API semble déjà exister: process.binding('util').getPromiseDetails( promise )retours [ 0, ]pour en attente, [ 1, value ]pour rempli et [ 2, value ]pour rejeté.
Matthijs
3

Attention: cette méthode utilise des composants internes Node.js non documentés et peut être modifiée sans avertissement.

Dans Node, vous pouvez déterminer de manière synchrone l'état d'une promesse à l'aide de process.binding('util').getPromiseDetails(/* promise */);.

Cela retournera:

[0, ] pour en attente,

[1, /* value */] pour accompli, ou

[2, /* value */] pour rejeté.

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

Enveloppant ceci dans une fonction d'assistance:

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

getStatus(pending); // pending
getStatus(fulfilled); // fulfilled
getStatus(rejected); // rejected
Scott Rudiger
la source
Cela ne semble pas fonctionner de l'intérieur jest(c'est le seul endroit qui m'intéresse, vraiment). La fonction existe, mais semble toujours revenir undefined. Comment savoir ce qui ne va pas?
Adam Barnes
Hmm, je me souviens que cela fonctionnait à l'intérieur mocha; jamais essayé avec jestcependant. Peut-être commencer une nouvelle question de lien ici et inclure votre version Node.js ainsi que la jestversion?
Scott Rudiger
Ce n'est plus quelque chose qui m'intéresse beaucoup plus, malheureusement. Je cherchais essentiellement à tester le bon sens de mon résolvable / rejetable manuellement Promiseque je n'utilisais que pour tester des choses qui devraient se dérouler pendant qu'une Promiseest en attente, mais j'ai pensé tant que ce que j'ai écrit fonctionne, alors il n'est pas nécessaire de le tester en plus de ce qui en dépend.
Adam Barnes
2

ce que vous pouvez faire, c'est utiliser une variable pour stocker l'état, définir manuellement l'état sur cette variable et vérifier cette variable.

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

bien sûr, cela signifie que vous devez avoir accès au code original de la promesse. Sinon, vous pouvez faire:

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

Ma solution est plus de codage, mais je pense que vous n'auriez probablement pas à le faire pour chaque promesse que vous utilisez.

Grand nom
la source
2

À partir de la version 8 de Node.js, vous pouvez désormais utiliser le package sage-inspection pour inspecter de manière synchrone les promesses natives (sans aucun piratage dangereux).

Joshua Wise
la source
2

Vous pouvez ajouter une méthode à Promise.prototype. Cela ressemble à ceci:

Modifié: La première solution ne fonctionne pas correctement, comme la plupart des réponses ici. Il renvoie «en attente» jusqu'à ce que la fonction asynchrone «.then» soit appelée, ce qui ne se produit pas immédiatement. (Il en va de même pour les solutions utilisant Promise.race). Ma deuxième solution résout ce problème.

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

Vous pouvez l'utiliser sur n'importe quelle promesse. Par exemple:

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

Deuxième solution (et correcte):

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

Et utilisez-le:

Remarque : Dans cette solution, vous n'avez pas besoin d'utiliser l'opérateur "new".

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

Voici une version es6 plus étoffée de QueryablePromise, permettant la possibilité de chaîner ensuite et d'attraper après la première résolution et de résoudre ou de rejeter immédiatement pour garder l'API cohérente avec la promesse native.

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
la source
1

awaitutilisation à la réponse de @ jib , avec prototypage idiomatique.

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

notez que cette fonction asynchrone exécute "presque" immédiatement comme une fonction synchronisée (ou peut-être même instantanément).

Valen
la source
1

2019:

Le moyen le plus simple de le faire, comme je le sais, est thenableune enveloppe super fine autour de la promesse ou de tout travail asynchrone.

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
la source
1

Vous pouvez extendla classe Promise pour créer une nouvelle classe Promise interrogeable .

Vous pouvez créer votre propre sous-classe, par exemple QueryablePromise, en héritant de la Promiseclasse disponible nativement , dont les instances auraient une statuspropriété disponible que vous pouvez utiliser pour interroger l'état des objets de promesse de manière synchrone . Une implémentation de celui-ci peut être vue ci-dessous ou se référer à ceci pour une meilleure explication.

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
la source
Malheureusement, aucune API existante ne renverra cette nouvelle classe. Comment imaginez-vous que les gens l'utilisent?
jib
@jib Merci pour votre réponse. Que voulez-vous dire qu'aucune API ne renverrait cette classe? :(
UtkarshPramodGupta
Aucune API existante ne le renverra, car il faudrait les écrire pour le retourner, non? Par exemple, si je l'appelle, fetchil renverra une promesse native. Comment votre classe vous aiderait-elle?
jib
Eh bien, nous ne pouvons pas simplement conclure que dans notre appel d' extraction nouvelle QuerablePromise comme: const queryableFetch = new QueryablePromise((resolve, reject) => {fetch(/.../).then((data) => resolve(data)) })? Ou y a-t-il un problème avec cela? : /
UtkarshPramodGupta
Cela devrait fonctionner, n'oubliez pas , err => reject(err)comme deuxième argument de thenou cela ne propagera pas correctement les erreurs (parmi les raisons pour lesquelles il est considéré comme l' anti-modèle du constructeur de promesse ). Ce n'est pas vraiment synchrone (par exemple, ne détecte pas une promesse déjà résolue), mais peut-être utile dans les cas où vous ne contrôlez pas l'appelant et que la réponse est nécessaire immédiatement.
jib
1

Il y a une autre élégante façon et hacky de vérifier si une promesse est toujours en attente juste en convertissant l'objet entier à la chaîne et vérifiez avec l'aide de l' inspection comme ceci: util.inspect(myPromise).includes("pending").

Testé sur Node.js 8,9,10,11,12,13

Voici un exemple complet

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

Résultat:

true
true
false
false
false
devio
la source
0

Si vous utilisez ES7 expérimental, vous pouvez utiliser async pour encapsuler facilement la promesse que vous souhaitez écouter.

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
la source
0

J'ai écrit un petit package npm, promise-value, qui fournit un wrapper de promesse avec un resolvedindicateur:

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

Il donne également un accès synchrone à la valeur de promesse (ou erreur). Cela ne modifie pas l'objet Promise lui-même, en suivant le modèle d'habillage plutôt que d'étendre.

Daniel Winterstein
la source
0

C'est une question plus ancienne mais j'essayais de faire quelque chose de similaire. J'ai besoin de garder n travailleurs. Ils sont structurés dans une promesse. Je dois scanner et voir s'ils sont résolus, rejetés ou toujours en attente. Si résolu, j'ai besoin de la valeur, si rejeté, faites quelque chose pour corriger le problème ou en attente. En cas de résolution ou de rejet, je dois commencer une autre tâche pour continuer. Je ne peux pas trouver un moyen de le faire avec Promise.all ou Promise.race car je continue à travailler les promesses dans un tableau et ne trouve aucun moyen de les supprimer. Alors je crée un ouvrier qui fait l'affaire

J'ai besoin d'une fonction de générateur de promesse qui renvoie une promesse qui résout ou rejette si nécessaire. Il est appelé par une fonction qui met en place le cadre pour savoir ce que fait la promesse.

Dans le code ci-dessous, le générateur renvoie simplement une promesse basée sur setTimeout.

C'est ici

//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 renvoie un objet contenant la promesse, son état et sa valeur retournée.

Le code suivant exécute une boucle qui teste l'état et crée de nouveaux nœuds de calcul pour le maintenir à 3 nœuds de calcul en cours d'exécution.

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

Testé dans node.js

BTW Pas tellement dans cette réponse, mais dans d'autres sur des sujets similaires, je déteste quand quelqu'un dit «vous ne comprenez pas» ou «ce n'est pas comme ça que ça marche». Je suppose généralement que le questionneur sait ce qu'il veut. Suggérer une meilleure façon, c'est super. Une explication patiente du fonctionnement des promesses serait également une bonne chose.

Charles Bisbee
la source
-1

J'ai trouvé que cette solution était simple et me permettait de continuer à utiliser les promesses natives tout en ajoutant des vérifications synchrones utiles. Je n'avais pas non plus besoin de récupérer une bibliothèque de promesses complète.

CAVEAT: Cela ne fonctionne que s'il y a une sorte de rupture dans le thread d'exécution actuel pour permettre aux promesses de s'exécuter AVANT de vérifier les constructions synchrones. Cela rend cela d'une utilité plus limitée que je ne le pensais initialement - toujours utile pour mon cas d'utilisation (merci à Benjamin Gruenbaum pour l'avoir signalé)

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

De https://ourcodeworld.com/articles/read/317/how-to-check-if-a-javascript-promise-has-been-fulfilled-rejected-or-resolved qui a basé leur réponse sur Y a-t-il un moyen de dire si une promesse ES6 est remplie / rejetée / résolue?

Akrikos
la source
Comme ajouté dans votre commentaire sur ma réponse - c'est tout à fait faux: cela ne vous permet pas d'inspecter de manière synchrone l'état d'une promesse - Par exemple, MakeQueryablePromise(Promise.resolve(3)).isResolvedc'est faux mais la promesse est bien évidemment résolue. Sans oublier que la réponse utilise également les termes «résolu» et «rempli» de manière incorrecte. Pour ce faire, vous pouvez simplement ajouter un .thengestionnaire vous-même - ce qui manque complètement le point d'inspection synchrone.
Benjamin Gruenbaum
Je vois ce que vous dites et vous faites valoir un bon point. La nature à un seul thread de JS fait obstacle, n'est-ce pas? Vous devez interrompre l'exécution en cours pour que la promesse soit marquée comme résolue. let wrappedPromise = MakeQueryablePromise(Promise.resolve(3)); setTimeout(function() {console.log(wrappedPromise.isFulfilled())}, 1);Et tant que vous faites cela, cela fonctionne bien. Mais vous devez comprendre ce fait pour que cela soit utile. Je mettrai à jour la description avec cette mise en garde. Je conviens également que la dénomination des fonctions pourrait être meilleure / plus idiomatique.
Akrikos
Mais à ce stade, vous pouvez simplement faire thenla promesse originale et accomplir la même chose car elle est de toute façon asynchrone. Il y a un moyen avec process.binding('util').getPromiseDetailscela semble fonctionner mais il utilise une API privée
Benjamin Gruenbaum
C'est odieux d'avoir à le faire tout le temps et cela rend le code beaucoup plus difficile à comprendre. Surtout quand je me soucie de savoir si la promesse a été rejetée ou non - mes options sont donc de stocker cet état ailleurs ou de faire quelque chose comme ça. J'avoue que je n'ai pas lu attentivement les autres solutions ici avant de publier les miennes - excuses pour cela. Ce problème est plus collant que je ne le pensais au premier abord.
Akrikos