web-dev-qa-db-fra.com

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

J'ai une promesse JavaScript pure (implémentation intégrée ou remplissage multiple):

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

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

  • la promesse est-elle tenue?

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

Je sais que je peux utiliser #then() pour planifier l'exécution d'un travail 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 interrogation synchrone de l'état d'une promesse . Comment puis-je atteindre cet objectif?

94
jokeyrhyme

Aucune API d'inspection synchrone de ce type n'existe pour les promesses JavaScript natives. Il est impossible de faire cela avec des promesses autochtones. 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 platform (vous pouvez écrire du code dans core), vous pouvez utiliser des outils spécifiques. (comme des symboles privés) pour y parvenir. C'est très spécifique cependant et pas dans l'utilisateur.

52

 enter image description here

promise-status-async fait le tour. Il est asynchrone mais n'utilise pas then pour 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;
}
30
0xaB

Vous pouvez faire une course avec Promise.resolve
Ce n'est pas synchrone mais ça arrive 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 manière 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 délai (0) (commentez le délai)

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 la commande)

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 font .race et .then: niveau 2

15
Steween

Non, pas d'API de synchronisation, mais voici ma version de la variable 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

14
jib

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

C'est en effet assez gênant que cette fonctionnalité de base soit manquante. Si vous utilisez node.js, je connais deux solutions de contournement, ni très jolies. 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 semble n'y avoir aucun moyen de distinguer les deux derniers états de promesse en utilisant l'une ou l'autre astuce.

1. Utiliser l'API de débogage V8

C'est le même truc que util.inspect utilise.

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. Microtasks exécutés de manière synchrone

Cela évite l'API de débogage, mais a une sémantique effrayante en faisant en sorte que tous les micro-tâches en attente et les rappels process.nextTick soient exécutés de manière synchrone. Cela a également pour effet secondaire d'empêcher que l'erreur de "rejet de promesse non gérée" ne soit jamais déclenchée pour la promesse vérifié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 };
};
4
Matthijs

Vous pouvez emballer 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;
}
4
SpiderPig

À partir de la version 8 de Node.js, vous pouvez maintenant utiliser le paquet wise-inspection pour inspecter de manière synchrone les promesses natives (sans aucun piratage dangereux).

3
Joshua Wise

Avertissement: cette méthode utilise des éléments internes non documentés de Node.js et peut être modifiée sans avertissement.

Dans Nœud, 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 remplir, 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']

Envelopper 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
3
Scott Rudiger

dans le noeud, dites process.binding('util').getPromiseDetails(promise)

2
amara

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 entendu, cela signifie que vous devez avoir accès au code original de la promesse. Si vous ne le faites pas, alors 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 davantage de codage, mais je pense que vous n’auriez probablement pas à le faire pour chaque promesse que vous utilisez.

2
BigName

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

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

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 fournies ici. Il renvoie "en attente" jusqu'à ce que la fonction asynchrone ".then" soit appelée, ce qui ne se produit pas immédiatement. (La même chose concerne 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: 

Avis : Dans cette solution, vous n'avez pas à utiliser le "nouvel" opérateur. 

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

C'est une question plus ancienne mais j'essayais de faire quelque chose de similaire. Je dois garder n travailleurs. Ils sont structurés dans une promesse. Je dois numériser 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 suspens. Si résolu ou 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 de travailler aux promesses dans un tableau et je ne trouve aucun moyen de les supprimer. Alors je crée un ouvrier qui fait le tour

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

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 renvoyée.

Le code suivant exécute une boucle qui teste l'état et crée de nouveaux employés pour le maintenir à 3 employés 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 un meilleur moyen, c'est génial. Une explication patiente du fonctionnement des promesses serait également utile. 

0
Charles Bisbee

Si vous utilisez ES7 expérimental, vous pouvez utiliser async pour emballer 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;
}
0

await usage à 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:
(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"
});

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

0
Valen

J'ai écrit un petit paquet npm, promise-value, qui fournit un wrapper de promesse avec un indicateur resolved:

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, suivant le bouclage plutôt que d'étendre le motif.

0
Daniel Winterstein

Voici une version es6 plus détaillée de QueryablePromise, permettant de chaîner puis de rattraper après la première résolution et de résoudre ou de rejeter immédiatement afin de 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>

0
synthet1c