Como posso determinar de forma síncrona o estado de uma promise de JavaScript?

Eu tenho um Promessa de JavaScript puro (implementação integrada ou preenchimento de poligênios):

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

A partir da especificação , um Promise pode ser um dos seguintes:

  • ‘resolvido’ e ‘resolvido’
  • ‘resolvido’ e ‘rejeitado’
  • ‘pendente’

Eu tenho um caso de uso em que desejo interrogar o Promise em sincronia e determinar:

  • a promise está resolvida?

  • Em caso afirmativo, a promise é resolvida?

Eu sei que posso usar #then() para agendar o trabalho a ser executado de forma assíncrona após o estado Promise changes. Eu não estou perguntando como fazer isso.

Esta questão é especificamente sobre a interrogação síncrona do estado de uma promise . Como posso conseguir isso?

Nenhuma API de inspeção síncrona existe para promises nativas de JavaScript. É impossível fazer isso com promises nativas. A especificação não especifica tal método.

As bibliotecas Userland podem fazer isso, e se você está alvejando um mecanismo específico (como v8) e tem access ao código da plataforma (ou seja, você pode escrever código no núcleo ), você pode usar ferramentas específicas (como símbolos privados) . Isso é super específico e não no mundo dos usuários.

Você pode fazer uma corrida com Promise.resolve
Não é sincrônico, mas acontece agora

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

Um pequeno script para testar e entender seu significado de forma assíncrona

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

resultados com atraso (0) (comente o tempo em atraso)

 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 os resultados deste teste com o firefox (chrome keep the order)

 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 

O promiseState faz o .race e o.

Não, nenhuma API de synchronization, mas aqui está minha versão do async promiseState (com ajuda do @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 

Você pode usar um hack (feio) no Node.js até que um método nativo seja oferecido:

 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 {  }') { console.log('pending'); // pending } if (state2 === "Promise { 'foo' }") { console.log ('foo') // foo } 

Você pode quebrar suas promises desta maneira

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

É realmente muito chato que esta funcionalidade básica esteja faltando. Se você estiver usando node.js, então eu sei de duas soluções alternativas, nenhuma delas muito bonita. Os dois snippets abaixo implementam a mesma 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' } 

Não parece haver nenhuma maneira de distinguir os dois últimos estados de promise usando um truque.

1. Use a API de debugging V8

Esse é o mesmo truque que o util.inspect usa.

 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. Executar microtarefas de forma síncrona

Isso evita a API de debugging, mas apresenta algumas semânticas assustadoras, fazendo com que todas as microtasks pendentes e retornos de chamada process.nextTick sejam executados de forma síncrona. Ele também tem o efeito colateral de impedir que o erro “rejeição de promises não tratadas” seja acionado para a promise inspecionada.

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

O Bluebird.js oferece isto: http://bluebirdjs.com/docs/api/isfulfilled.html

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

O que você pode fazer é usar uma variável para armazenar o estado, definir manualmente o estado para essa variável e verificar essa variável.

 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 

claro, isso significa que você deve ter access ao código original da promise. Se você não fizer isso, então você pode fazer:

 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 

Minha solução é mais codificação, mas acho que você provavelmente não teria que fazer isso para cada promise que você usa.

Desde a versão 8 do Node.js, agora você pode usar o pacote wise-inspection para inspecionar de forma síncrona as promises nativas (sem hacks perigosos).

Você pode adicionar um método ao Promise.prototype. Se parece com isso:

Editado: A primeira solução não está funcionando corretamente, como a maioria das respostas aqui. Ele retorna “pendente” até que a function assíncrona “.then” seja chamada, o que não acontece imediatamente. (O mesmo é sobre soluções usando Promise.race). Minha segunda solução resolve esse 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; }; } 

Você pode usá-lo em qualquer promise. Por exemplo:

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

Segunda solução (e correta):

 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 use:

Aviso : Nesta solução, você não precisa usar o operador “novo”.

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

no nó, diga process.binding('util').getPromiseDetails(promise)

Se você está usando o ES7 experimental, você pode usar async para facilmente quebrar a promise que deseja ouvir.

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

Eu escrevi um pequeno pacote npm, promise-valor, que fornece um empacotador de promise com um sinalizador resolved :

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

Também fornece access síncrono ao valor da promise (ou erro). Isso não altera o próprio object Promise, seguindo o padrão wrap em vez de estender.

Esta é uma pergunta mais antiga, mas eu estava tentando fazer algo semelhante. Eu preciso manter n trabalhadores indo. Eles estão estruturados em uma promise. Preciso varrer e ver se eles estão resolvidos, rejeitados ou ainda estão pendentes. Se resolvido, preciso do valor, caso seja rejeitado faça alguma coisa para corrigir o problema ou pendente. Se resolvido ou rejeitado, preciso iniciar outra tarefa para continuar. Eu não consigo descobrir uma maneira de fazer isso com Promise.all ou Promise.race enquanto continuo trabalhando promises em uma matriz e não consigo encontrar nenhuma maneira de excluí-los. Então eu crio um trabalhador que faz o truque

Eu preciso de uma function de gerador de promise que retorna uma promise que resolve ou rejeita conforme necessário. É chamado por uma function que configura a estrutura para saber o que a promise está fazendo.

No código abaixo, o gerador simplesmente retorna uma promise baseada em setTimeout.

Aqui está

 //argObj should be of form // {succeed: } 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; } 

O doWork retorna um object contendo a promise e seu estado e valor retornado.

O código a seguir executa um loop que testa o estado e cria novos trabalhadores para mantê-lo em três funcionários em execução.

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

Testado em node.js

BTW Não nesta resposta tanto, mas em outros sobre temas semelhantes, eu odeio quando alguém diz “você não entende” ou “não é assim que funciona” Eu geralmente presumo que o questionador sabe o que eles querem. Sugerir um caminho melhor é ótimo. Uma explicação paciente de como as promises funcionam também seria boa.

Eu encontrei esta solução para ser simples e permitir-me continuar usando promises nativas, mas adicionar verificações síncronas úteis. Eu também não precisei comprar uma biblioteca inteira de promises.

CAVEAT: Isso só funciona se houver algum tipo de quebra no thread de execução atual para permitir que as promises sejam executadas ANTES de verificar as construções síncronas. Isso torna isso uma utilidade mais limitada do que eu pensava inicialmente – ainda é útil para o meu caso de uso (Obrigado Benjamin Gruenbaum por apontar isso)

 /** * 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-ha-been-fulfilled-rejected-or-resolved que baseou a resposta em Existe uma maneira de diga se uma promise ES6 é cumprida / rejeitada / resolvida?