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

149

Eu tenho uma promessa de JavaScript puro (implementação interna ou preenchimento múltiplo):

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

A partir da especificação , uma promessa pode ser uma das seguintes:

  • 'resolvido' e 'resolvido'
  • 'resolvido' e 'rejeitado'
  • 'pendente'

Eu tenho um caso de uso em que desejo interrogar a Promessa de forma síncrona e determinar:

  • a promessa está estabelecida?

  • Nesse caso, a promessa foi resolvida?

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

Esta pergunta é especificamente sobre interrogatório síncrono do estado de uma promessa . Como posso conseguir isso?

jokeyrhyme
fonte
6
defina uma propriedade sob a promessa que pode ser vista de fora e use then () para alterar a propriedade.
Dandavis
@jokeyrhyme fwiw, v8 source code.google.com/p/v8/source/browse/branches/bleeding_edge/src/… consulte var promiseStatus = NEW_PRIVATE("Promise#status");, PromiseSetfunção emSET_PRIVATE(promise, promiseStatus, status);
guest271314
Aqui vamos nós: esdiscuss.org/topic/…
jokeyrhyme
Parece estranho que, se você fizer const a = Promise.resolve ('baz'); console.log (a); e veja no console do Chrome, você vê Promise {[[PromiseStatus]]: "resolvido", [[PromiseValue]]: "baz"} proto: Promise [[PromiseStatus]]: "resolved" [[PromiseValue]]: "baz "e as pessoas afirmam que isso não pode ser feito. Como o Chrome está fazendo isso? (estava fazendo isso em um Plunker com Angular plnkr.co/edit/IPIWgLJKQStI5ubXmcsF
JGFMK
O uso do nó v11.12.0 console.log mostrará o estado da promessa. EG console.log(Promise.new((resolve, reject) => {})=>Promise { <pending> }
Puhlze 17/05/19

Respostas:

77

Não existe uma API de inspeção síncrona para promessas nativas de JavaScript. É impossível fazer isso com promessas nativas. A especificação não especifica esse método.

As bibliotecas do Userland podem fazer isso e, se você estiver direcionando um mecanismo específico (como a v8) e tiver acesso ao código da plataforma (ou seja, você pode escrever o código no núcleo ), poderá usar ferramentas específicas (como símbolos particulares) para conseguir isso . Isso é super específico e não na área do usuário.

Benjamin Gruenbaum
fonte
4
Nota: Acredito sinceramente que os casos de uso para inspeção síncrona são poucos e muito raros, se você compartilhar seu caso de uso concreto em uma nova pergunta perguntando como alcançá-lo sem inspeção síncrona - darei uma resposta se alguém não quiser beat me to it :)
Benjamin Gruenbaum
4
Mesmo que os casos de uso sejam raros, que mal incluiria algo assim? Eu precisaria de uma verificação de status como esta para ver se o trabalho anterior foi concluído e se posso solicitar outro trabalho. E não posso simplesmente definir uma variável externa porque o objeto tem o potencial de alterar proprietários sem aviso prévio. O que é mais irritante é que POSSO VER O Node.js ter acesso a essas informações, porque elas são exibidas quando eu as inspeciono, mas não há como chegar além de analisar as strings?
Tustin2121 16/05
9
Portanto, devemos jogar fora as promessas nativas, pois elas são impraticáveis ​​e sempre usam o pássaro azul. Boas notícias! Como proponho promessas nativas a serem descontinuadas e descartadas do mecanismo do nó?
user619271
1
Muitas coisas, deveríamos ter especulado .anye cometido um erro porque Mark insistiu. Por um lado, Promise.race([])é uma promessa pendente para sempre (e não um erro), normalmente você deseja a primeira promessa bem-sucedida e não apenas a primeira. Enfim, isso não é realmente relevante para a pergunta - o OP perguntou sobre inspeção síncrona e não sobre .racee suas muitas deficiências.
Benjamin Gruenbaum 15/10
5
@Akrikos que responde não permite que você inspecione de forma síncrona o estado de uma promessa - por exemplo, MakeQueryablePromise(Promise.resolve(3)).isResolvedé falso, mas a promessa é obviamente resolvida. Sem mencionar que a resposta também está usando o termo "resolvido" e "preenchido" incorretamente. Para fazer essa resposta, basta adicionar um .thenmanipulador - o que ignora completamente o ponto da inspeção síncrona.
Benjamin Gruenbaum
31

insira a descrição da imagem aqui

promessa-status-assíncrona faz o truque. É assíncrono, mas não costuma thenesperar a promessa de ser resolvida.

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
fonte
4
OP perguntou sobre como fazê-lo de forma síncrona embora
Klesun 31/05/19
28

Não, não há API de sincronização, mas aqui está a minha versão do assíncrono promiseState(com a ajuda 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

jib
fonte
Existe um raciocínio específico por trás dessa construção? Parece desnecessariamente complicado para mim. Tanto quanto posso dizer, isso funciona de forma idêntica: Promise.race([ Promise.resolve(p).then(() => "fulfilled", () => "rejected"), Promise.resolve().then(() => "pending") ]); Embora isso pareça mais seguro para mim: const t = {}; return Promise.race([p,t]).then(v => v === t ? "pending" : "fulfilled", () => "rejected") evita a criação de promessas adicionais que persistem enquanto o p original estiver pendente.
Matthijs 12/06
Obrigado @Matthijs! Eu simplifiquei minha resposta.
lança
16

Você pode fazer uma corrida com Promise.resolve
Não é síncrono, 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 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 (o chrome mantém a ordem)

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

promessaState faça .race e .then: Nível 2

Steween
fonte
3
Em vez de 'a value that p should not return', use um símbolo
programmer5000
1
@ programmer5000 Qual é o benefício?
Moritz Schmitz v. Hülst 5/10
2
@ MoritzSchmitzv.Hülst a Symbolseria um valor único, portanto, você nunca precisaria adivinhar qual "valor [...] p não deve retornar". No entanto, uma referência a um objeto específico também funcionaria.
22618 Scott Schaffield
7

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 { <pending> }') {

    console.log('pending'); // pending
}

if (state2 === "Promise { 'foo' }") {

    console.log ('foo') // foo
}
rabbitco
fonte
3
Eu fervida para baixo a um polyfill:Promise.prototype.isPending = function(){ return util.inspect(this).indexOf("<pending>")>-1; }
Tustin2121
5
Isso é horrível .
John Weisz
@JohnWeisz O que é horrível é a falta de compatibilidade com versões anteriores. Estou tentando integrar uma API promissora a uma base de código que assume que tudo é síncrono. Ou está fazendo algo horrendo ou reescrevendo grandes pedaços de código. De qualquer maneira, estou cometendo uma atrocidade.
Rath3 de
4
apenas useprocess.binding('util').getPromiseDetails
amara
@ Tustin2121 Para algumas versões, falhará com algo parecido Promise.resolve('<pending>').
user202729 24/01
7

no nó, digamos interno não documentado 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
fonte
Eu adicionei isso porque não estava em nenhuma das respostas existentes e, para o nó, é a melhor resposta. é fácil procurar os documentos em github.com/nodejs/node #
amara
6

Atualizado: 2019

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

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

Se você preferir criar seu próprio wrapper, aqui está um bom blog .

Como o JavaScript é de thread único, é difícil encontrar um caso de uso comum o suficiente para justificar colocar isso na especificação. O melhor lugar para saber se uma promessa é resolvida é em .then (). Testar se uma promessa é cumprida criaria um loop de sondagem que provavelmente é a direção errada.

assíncrono / espera é uma construção interessante se você quiser raciocinar código assíncrono de forma síncrona.

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

Outra chamada útil é Promise.all ()

var promise1 = Promise.resolve(3);
var promise2 = 42;
var promise3 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then(function(values) {
  console.log(values);
});
// expected output: Array [3, 42, "foo"]

Quando cheguei a esta resposta, esse é o caso de uso que eu estava procurando.

Michael Cole
fonte
5

Você pode cumprir suas promessas dessa 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;
}
Porco Aranha
fonte
5
Isso exigiria que o OP tivesse acesso à promessa em uma etapa anterior do loop de eventos . Como .thensempre executa OP de forma assíncrona que deseja inspecionar uma promessa no mesmo turno não obterá o resultado correto aqui. Nota O OP perguntou especificamente sobre inspeção síncrona e mencionou que eles já sabem sobre inspeção assíncrona.
Benjamin Gruenbaum
@BenjaminGruenbaum: os valores padrão não apareceriam se o código no mesmo "turn" o chamasse?
Dandavis
Claro que você teria que cumprir todas as suas promessas no momento da criação. por exemplo, dentro das funções que as criam e as devolvem.
SpiderPig #
3
Certo, nesse ponto, elas não são mais promessas nativas, você também pode estendê-las da maneira como devem ser estendidas com subclasses, o que permitiria fazer isso com elegância, em vez de aplicar as propriedades dos patches em um objeto.
Benjamin Gruenbaum
Independentemente de você estender uma promessa da maneira que mostrei ou subclassificando, em cada caso, você ainda precisará adicionar sua própria versão e capturar.
SpiderPig #
5

É realmente muito irritante que essa funcionalidade básica esteja ausente. Se você estiver usando o node.js, conheço duas soluções alternativas, nenhuma delas muito bonita. Ambos os trechos 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 promessa usando qualquer um dos truques.

1. Use a API de depuração V8

Este é o mesmo truque que util.inspectusa.

const Debug = require('vm').runInDebugContext('Debug');

Promise.getInfo = function( arg ) {
    let mirror = Debug.MakeMirror( arg, true );
    if( ! mirror.isPromise() )
        return { status: 'fulfilled', value: arg };
    let status = mirror.status();
    if( status === 'pending' )
        return { status };
    if( status === 'resolved' )  // fix terminology fuck-up
        status = 'fulfilled';
    let value = mirror.promiseValue().value();
    return { status, value };
};

2. Execute microtarefas de forma síncrona

Isso evita a API de depuração, mas possui algumas semânticas assustadoras, fazendo com que todas as microtasks e process.nextTickretornos de chamada pendentes sejam executados de forma síncrona. Ele também tem o efeito colateral de impedir que o erro "rejeição de promessa não tratada" seja acionado para a promessa 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 };
};
Matthijs
fonte
É muito inseguro de fazer process._tickCallback(ou mesmo% RunMicrotick) - ele irá quebrar as coisas aleatoriamente no seu código. Tentei desesperadamente fazê-lo funcionar (principalmente para temporizadores falsos em funções assíncronas) e nunca foi suficientemente estável do lado do Node. Eu meio que desisti de trabalhar nisso. A API do espelho de depuração V8 é totalmente apropriada aqui.
Benjamin Gruenbaum
E .. DeprecationWarning: DebugContext has been deprecated and will be removed in a future version.:( Parece que o V8 o removeu
Benjamin Gruenbaum
No entanto, nós (Node) podemos solicitar totalmente uma API à V8 ou expor uma API para analisar diretamente o estado de uma promessa - se você abrir um problema em github.com/nodejs/promise-use-cases , apresentarei o V8 com satisfação
Benjamin Gruenbaum
1
Um comentário mais abaixo neste tópico revelou que uma API já parece existir: process.binding('util').getPromiseDetails( promise )retornos [ 0, ]para pendentes, [ 1, value ]preenchidos e [ 2, value ]rejeitados.
Matthijs
3

Advertência: Esse método usa elementos internos não documentados do Node.js. e pode ser alterado sem aviso.

No Nó, você pode determinar de forma síncrona o estado de uma promessa usando process.binding('util').getPromiseDetails(/* promise */);.

Isso retornará:

[0, ] pendente,

[1, /* value */] cumpridos, ou

[2, /* value */] para rejeitado.

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

Agrupando isso em uma função auxiliar:

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

getStatus(pending); // pending
getStatus(fulfilled); // fulfilled
getStatus(rejected); // rejected
Scott Rudiger
fonte
Não parece funcionar de dentro jest(que é o único lugar em que estou realmente interessado). A função existe, mas sempre parece retornar undefined. Como descubro o que há de errado?
Adam Barnes
Hmm, eu lembro disso trabalhando dentro mocha; nunca tentei com isso jest. Talvez comece uma nova pergunta vinculando aqui e inclua sua versão do Node.js. e também a jestversão?
Scott Rudiger
Infelizmente, isso não me interessa muito. Eu estava basicamente procurando testar minha sanidade manualmente resolvível / rejeitável Promiseque estava usando apenas para testar coisas que deveriam estar acontecendo enquanto um Promiseestá pendente, mas achei que, enquanto o que escrevi funcionasse, não haveria necessidade de testar isso. além do que depende dele.
Adam Barnes
2

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 acesso ao código original da promessa. Caso contrário, 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 codificada, mas acho que você provavelmente não precisaria fazer isso para todas as promessas que usa.

Grande nome
fonte
2

A partir do Node.js. versão 8, agora você pode usar o pacote de inspeção inteligente para inspecionar de forma síncrona as promessas nativas (sem hacks perigosos).

Joshua Wise
fonte
2

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 função assíncrona ".then" seja chamada, o que não ocorre 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 promessa. Por exemplo:

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

Segunda (e correta) solução:

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

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

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

Aqui está uma versão es6 mais detalhada do QueryablePromise, permitindo a capacidade de encadear e capturar após a primeira resolução e resolver ou rejeitar imediatamente para manter a API consistente com a Promessa nativa.

const PROMISE = Symbol('PROMISE')
const tap = fn => x => (fn(x), x)
const trace = label => tap(x => console.log(label, x))

class QueryablePromise {
  resolved = false
  rejected = false
  fulfilled = false
  catchFns = []
  constructor(fn) {
    this[PROMISE] = new Promise(fn)
      .then(tap(() => {
        this.fulfilled = true
        this.resolved = true
      }))
      .catch(x => {
        this.fulfilled = true
        this.rejected = true
        return Promise.reject(x)
      })
  }
  then(fn) {
    this[PROMISE].then(fn)
    return this
  }
  catch(fn) {
    this[PROMISE].catch(fn)
    return this
  }
  static resolve(x) {
    return new QueryablePromise((res) => res(x))
  }
  static reject(x) {
    return new QueryablePromise((_, rej) => rej(x))
  }
}

const resolvedPromise = new QueryablePromise((res) => {
  setTimeout(res, 200, 'resolvedPromise')
})

const rejectedPromise = new QueryablePromise((_, rej) => {
  setTimeout(rej, 200, 'rejectedPromise')
})

// ensure our promises have not been fulfilled
console.log('test 1 before: is resolved', resolvedPromise.resolved)
console.log('test 2 before: is rejected', rejectedPromise.rejected)


setTimeout(() => {
  // check to see the resolved status of our promise
  console.log('test 1 after: is resolved', resolvedPromise.resolved)
  console.log('test 2 after: is rejected', rejectedPromise.rejected)
}, 300)

// make sure we can immediately resolve a QueryablePromise
const immediatelyResolvedPromise = QueryablePromise.resolve('immediatelyResolvedPromise')
  // ensure we can chain then
  .then(trace('test 3 resolved'))
  .then(trace('test 3 resolved 2'))
  .catch(trace('test 3 rejected'))

// make sure we can immediately reject a QueryablePromise
const immediatelyRejectedPromise = QueryablePromise.reject('immediatelyRejectedPromise')
  .then(trace('test 4 resolved'))
  .catch(trace('test 4 rejected'))
<script src="https://codepen.io/synthet1c/pen/KyQQmL.js"></script>

synthet1c
fonte
1

awaituso à resposta do @ jib , com prototipagem idiomática.

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

note que esta função assíncrona executa "quase" imediatamente como a função sincronizada (ou, na verdade, pode ser instantaneamente).

Valen
fonte
1

2019:

A maneira mais simples de fazer isso, como eu sei thenable, é o invólucro super fino em torno da promessa ou qualquer trabalho assíncrono.

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
fonte
1

Você pode extenda classe Promise para criar uma nova classe Promise consultável .

Você pode criar sua própria subclasse, digamos QueryablePromise, herdando da Promiseclasse disponível nativamente , cujas instâncias teriam uma statuspropriedade disponível que você pode usar para consultar o status dos objetos de promessa de forma síncrona . Uma implementação pode ser vista abaixo ou consulte isso para uma melhor explicação.

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
fonte
Infelizmente, nenhuma API existente retornará essa nova classe. Como você está imaginando que as pessoas usam isso?
jib
Obrigado pela sua resposta. Como assim, nenhuma API retornaria essa classe? :(
UtkarshPramodGupta
Nenhuma API existente o retornará, pois precisaria ser gravada para devolvê-lo, certo? Por exemplo, se eu ligar, fetchele retornará uma promessa nativa. Como sua classe ajudaria nisso?
jib
Bem, não podemos simplesmente encerrar essa chamada em nosso novo QuerablePromise como const queryableFetch = new QueryablePromise((resolve, reject) => {fetch(/.../).then((data) => resolve(data)) }):? Ou existe algum problema com isso? : /
UtkarshPramodGupta
Isso deve funcionar, apenas não esqueça , err => reject(err)como um segundo argumento thenou ele não propagará erros corretamente (entre os motivos pelos quais é considerado o antipadrão do construtor de promessas ). Porém, não é realmente síncrono (por exemplo, não detecta uma promessa já resolvida), mas talvez seja útil nos casos em que você não controla o chamador e a resposta é necessária imediatamente.
jib
1

Há outra maneira elegante e hacky de verificar se uma promessa ainda está pendente apenas convertendo todo o objeto em string e verificando-o com a ajuda de uma inspeção como esta:util.inspect(myPromise).includes("pending") .

Testado em Node.js 8,9,10,11,12,13

Aqui está um exemplo completo

const util = require("util")

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

(async ()=>{
  let letmesleep = sleep(3000)
  setInterval(()=>{
    console.log(util.inspect(letmesleep).includes("pending"))
  },1000)
})()

Resultado:

true
true
false
false
false
devio
fonte
0

Se você estiver usando o ES7 experimental, poderá usar o assíncrono para cumprir facilmente a promessa 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;
}
Ezequiel S. Pereira
fonte
0

Eu escrevi um pequeno pacote npm, promessa-valor, que fornece um wrapper de promessa com um resolved bandeira:

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

Também fornece acesso síncrono ao valor da promessa (ou erro). Isso não altera o próprio objeto Promise, seguindo o padrão de quebra, em vez de estender.

Daniel Winterstein
fonte
0

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

Preciso de uma função de gerador de promessas que retorne uma promessa que resolva ou rejeita conforme necessário. É chamado por uma função que configura a estrutura para saber o que a promessa está fazendo.

No código abaixo, o gerador simplesmente retorna uma promessa com base em setTimeout.

Aqui está

//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 retorna um objeto que contém a promessa 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 trabalhadores 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, mas em outras em tópicos semelhantes, eu odeio quando alguém diz "você não entende" ou "não é assim que funciona". Sugerir uma maneira melhor é ótimo. Uma explicação paciente de como as promessas funcionam também seria boa.

Charles Bisbee
fonte
-1

Achei esta solução simples e me permitiu continuar usando promessas nativas, mas adicionei verificações síncronas úteis. Também não precisei puxar uma biblioteca inteira de promessas.

CAVEAT: Isso funciona apenas se houver algum tipo de interrupção no segmento de execução atual para permitir que as promessas sejam executadas ANTES de verificar as construções síncronas. Isso torna isso de utilidade mais limitada do que eu pensava inicialmente - embora ainda seja útil para 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);

Em https://ourcodeworld.com/articles/read/317/how-to-check-if-a-javascript-promise-has-been-fulfilled-rejected-or-resolved que baseou sua resposta em Existe uma maneira de saber se uma promessa do ES6 foi cumprida / rejeitada / resolvida?

Akrikos
fonte
Conforme adicionado no seu comentário na minha resposta - isso é totalmente incorreto: isso não permite que você inspecione de forma síncrona o estado de uma promessa - Por exemplo, MakeQueryablePromise(Promise.resolve(3)).isResolvedé falso, mas a promessa é obviamente resolvida. Sem mencionar que a resposta também está usando o termo "resolvido" e "preenchido" incorretamente. Para fazer essa resposta, basta adicionar um .thenmanipulador - o que ignora completamente o ponto da inspeção síncrona.
Benjamin Gruenbaum
Entendo o que você está dizendo e você faz um bom argumento. A natureza de thread único do JS está atrapalhando, não é? É necessário interromper a execução atual para que a promessa seja marcada como resolvida. let wrappedPromise = MakeQueryablePromise(Promise.resolve(3)); setTimeout(function() {console.log(wrappedPromise.isFulfilled())}, 1);Que, desde que você faça isso, isso funciona muito bem. Mas você precisa entender esse fato para que isso seja útil. Vou atualizar a descrição com essa ressalva. Também concordo que a nomeação de funções poderia ser melhor / mais idiomática.
Akrikos
Mas, nesse ponto, você poderia thencumprir a promessa original e realizar a mesma coisa, já que é assíncrona. Existe uma maneira com process.binding('util').getPromiseDetailsque parece trabalho, mas é usando uma API privada
Benjamin Gruenbaum
É desagradável ter que fazer isso o tempo todo e torna o código muito mais difícil de entender. Especialmente quando tudo o que me interessa é se a promessa foi rejeitada ou não - então minhas opções são armazenar esse estado em outro lugar ou fazer algo assim. Admito que não li as outras soluções aqui completamente antes de postar minhas próprias - desculpas por isso. Esse problema é mais persistente do que eu pensava.
Akrikos