¿Cómo puedo determinar sincrónicamente el estado de una promesa de JavaScript?

149

Tengo una promesa de JavaScript pura (implementación integrada o relleno de polietileno):

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

De la especificación , una Promesa puede ser uno de:

  • 'resuelto' y 'resuelto'
  • 'resuelto' y 'rechazado'
  • 'pendiente'

Tengo un caso de uso en el que deseo interrogar a Promise sincrónicamente y determinar:

  • ¿Está resuelta la promesa?

  • Si es así, ¿se resuelve la promesa?

Sé que puedo usar #then()para programar el trabajo que se realizará de forma asíncrona después de que Promise cambie de estado. NO estoy preguntando cómo hacer esto.

Esta pregunta es específicamente sobre la interrogación sincrónica del estado de una promesa . ¿Cómo puedo conseguir esto?

broma
fuente
66
establezca una propiedad en la promesa que se puede ver desde afuera, y use then () para cambiar la propiedad.
dandavis 01 de
@jokeyrhyme fwiw, v8 código fuente.google.com/p/v8/source/browse/branches/bleeding_edge/src/… ver var promiseStatus = NEW_PRIVATE("Promise#status");, PromiseSetfunción enSET_PRIVATE(promise, promiseStatus, status);
guest271314
Aquí vamos: esdiscuss.org/topic/…
jokeyrhyme
Parece extraño que si const a = Promise.resolve ('baz'); console.log (a); y mira en la consola de Chrome, verá Promise {[[PromiseStatus]]: "resuelto", [[PromiseValue]]: "baz"} proto: Promise [[PromiseStatus]]: "resuelto" [[PromiseValue]]: "baz "Y la gente dice que no se puede hacer. ¿Cómo lo está haciendo Chrome? (estaba haciendo esto en un Plunker con Angular plnkr.co/edit/IPIWgLJKQStI5ubXmcsF
JGFMK
El uso del nodo v11.12.0 console.log mostrará el estado de promesa. EG console.log(Promise.new((resolve, reject) => {})=>Promise { <pending> }
Puhlze

Respuestas:

77

No existe tal API de inspección síncrona para las promesas nativas de JavaScript. Es imposible hacer esto con las promesas nativas. La especificación no especifica dicho método.

Las bibliotecas Userland pueden hacer esto, y si está apuntando a un motor específico (como v8) y tiene acceso al código de la plataforma (es decir, puede escribir código en el núcleo ), entonces puede usar herramientas específicas (como símbolos privados) para lograr esto . Sin embargo, eso es súper específico y no en el país de los usuarios.

Benjamin Gruenbaum
fuente
44
Nota: Sinceramente, creo que los casos de uso para la inspección síncrona son pocos y muy raros, si comparte su caso de uso concreto en una nueva pregunta que pregunta cómo lograrlo sin una inspección síncrona, le daré una respuesta si alguien no lo hace.
vencerme
44
Incluso si los casos de uso son raros, ¿qué daño incluiría algo como esto? Necesitaría una verificación de estado como esta para ver si el trabajo anterior estaba terminado y si puedo solicitar otro trabajo. Y no puedo simplemente establecer una variable externa porque el objeto tiene el potencial de cambiar de propietario sin previo aviso. Lo que es más irritante es que puedo VER que Node.js tiene acceso a esta información porque me la muestra cuando la inspecciono, pero ¿no hay forma de obtenerla además de analizar cadenas?
Tustin2121
9
Por lo tanto, debemos descartar las promesas nativas, ya que no son prácticas y siempre usan bluebird. ¡Una gran noticia! ¿Cómo propongo promesas nativas para quedar obsoletas y descartadas del motor de nodo?
user619271
1
Muchas cosas, deberíamos haber especificado .anyy cometido un error porque Mark insistió. Por un lado, Promise.race([])es una promesa pendiente para siempre (y no un error), generalmente desea la primera promesa exitosa y no solo la primera promesa. De todos modos, eso no es realmente relevante para la pregunta formulada: OP preguntó sobre la inspección sincrónica y no sobre .racesus muchas deficiencias.
Benjamin Gruenbaum
55
@Akrikos esa respuesta no le permite inspeccionar sincrónicamente el estado de una promesa: por ejemplo, MakeQueryablePromise(Promise.resolve(3)).isResolvedes falsa, pero la promesa se resuelve de manera bastante obvia. Sin mencionar que la respuesta también está usando el término "resuelto" y "cumplido" incorrectamente. Para hacer esa respuesta, simplemente puede agregar un .thencontrolador usted mismo, que omite por completo el punto de inspección sincrónica.
Benjamin Gruenbaum
31

ingrese la descripción de la imagen aquí

promise-status-async hace el truco. Es asíncrono pero no sirve thenpara esperar la promesa de ser resuelta.

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
fuente
44
OP preguntó acerca de cómo hacerlo sincrónicamente
Klesun
28

No, no hay API de sincronización, pero aquí está mi versión de la asíncrona promiseState(con ayuda 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

foque
fuente
¿Hay un razonamiento específico detrás de esta construcción? Me parece innecesariamente complicado. Por lo que puedo decir, esto funciona de manera idéntica: Promise.race([ Promise.resolve(p).then(() => "fulfilled", () => "rejected"), Promise.resolve().then(() => "pending") ]); aunque esto me parece más seguro: const t = {}; return Promise.race([p,t]).then(v => v === t ? "pending" : "fulfilled", () => "rejected") y evita crear promesas adicionales que persisten mientras el p original esté pendiente.
Matthijs
Gracias @Matthijs! He simplificado mi respuesta.
foque
16

Puedes hacer una carrera con Promise.resolve
No es sincrónico pero sucede ahora

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 pequeño script para probar y comprender su significado de forma asincrónica

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 con retraso (0) (comentar el tiempo de retraso)

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

y los resultados de esta prueba con firefox (Chrome mantiene el orden)

00000 end of promises
00100 end of script
00300 Level 1 : One
00300 Level 1 : Three
00400 p1 Is Resolved One
00400 p2 Is Pending undefined
00400 p3 Is Rejected Three
00400 Level 2 : One
00400 Level 2 : Three
00400 delayed L3 : Three
00400 Level 3 : One
00400 Level 3 : Three
00406 timeOut 0 ms
00406 timeOut 100 ms
00406 timeOut 200 ms
00406 Level 1 : Two
00407 Level 2 : Two
00407 Level 3 : Two

promiseState make .race y .then: Level 2

Steween
fuente
3
En lugar de 'a value that p should not return'usar un Símbolo
programmer5000
1
@ programmer5000 ¿Cuál es el beneficio?
Moritz Schmitz v. Hülst
2
@ MoritzSchmitzv.Hülst a Symbolsería un valor único, por lo tanto, nunca tendría que adivinar qué "valor [...] p no debería devolver". Sin embargo, una referencia a un objeto específico funcionaría igual de bien.
Scott Rudiger el
7

Puede usar un truco (feo) en Node.js hasta que se ofrezca un método nativo:

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
fuente
3
Lo he reducido a un polyfill:Promise.prototype.isPending = function(){ return util.inspect(this).indexOf("<pending>")>-1; }
Tustin2121
55
Eso es horrendo .
John Weisz
@JohnWeisz Lo horrendo es la falta de compatibilidad con versiones anteriores. Estoy tratando de integrar una API prometedora a una base de código que asume que todo es sincrónico. O está haciendo algo horrible o reescribiendo enormes fragmentos de código. De cualquier manera estoy cometiendo una atrocidad.
rath
44
solo useprocess.binding('util').getPromiseDetails
amara
@ Tustin2121 Para alguna versión fallará con algo como Promise.resolve('<pending>').
user202729
7

en el nodo, digamos interno no 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
fuente
Agregué esto porque no estaba en ninguna de las respuestas existentes, y para el nodo es la mejor respuesta. es fácil buscar los documentos en github.com/nodejs/node
amara el
6

Actualizado: 2019

Bluebird.js ofrece esto: http://bluebirdjs.com/docs/api/isfulfilled.html

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

Si prefiere crear su propio contenedor, aquí hay un buen blog al respecto.

Debido a que JavaScript es de subproceso único, es difícil encontrar un caso de uso lo suficientemente común como para justificar poner esto en la especificación. El mejor lugar para saber si se resuelve una promesa es en .then (). Probar si se cumple una Promesa crearía un bucle de sondeo que probablemente sea la dirección incorrecta.

async / await es una buena construcción si desea razonar el código asincrónico sincrónicamente.

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

Otra llamada útil es 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"]

Cuando busqué por primera vez esta respuesta, ese es el caso de uso que estaba buscando.

Michael Cole
fuente
5

Puedes envolver tus promesas de esta manera

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;
}
Puerco araña
fuente
55
Esto requeriría que OP tuviera acceso a la promesa en un giro anterior del bucle de eventos . Dado que .thensiempre ejecuta OP asíncronamente, quien quiera inspeccionar una promesa en el mismo turno no obtendrá el resultado correcto aquí. Nota OP preguntó específicamente sobre la inspección sincrónica y mencionó que ya saben acerca de la inspección asincrónica.
Benjamin Gruenbaum
@BenjaminGruenbaum: ¿no aparecerían los valores predeterminados si el código en el mismo "turno" lo llamara?
dandavis 01 de
Por supuesto, tendría que completar todas sus promesas en el momento de la creación. por ejemplo, dentro de las funciones que las crean y devuelven.
SpiderPig
3
Bien, en ese momento ya no son promesas nativas, también podría extenderlas de la manera en que deberían extenderse con subclases que le permitirían hacer esto elegantemente en lugar de las propiedades de parcheado de mono en un objeto.
Benjamin Gruenbaum
Ya sea que extienda una promesa de la manera que mostré o subclasificando, en cada caso aún tendría que agregar su propia versión de entonces y capturar.
SpiderPig
5

De hecho, es bastante molesto que falte esta funcionalidad básica. Si está utilizando node.js, sé dos soluciones alternativas, ninguna de las cuales es muy bonita. Ambos fragmentos a continuación implementan la misma 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' }

No parece haber ninguna forma de distinguir los dos últimos estados de promesa utilizando cualquiera de los trucos.

1. Use la API de depuración V8

Este es el mismo truco 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. Ejecute sincrónicamente microtasks

Esto evita la API de depuración, pero tiene una semántica aterradora al hacer que todas las microtasks y las process.nextTickdevoluciones de llamada pendientes se ejecuten sincrónicamente. También tiene el efecto secundario de evitar que se active el error de "rechazo de promesa no manejada" para la promesa inspeccionada.

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
fuente
Es muy inseguro hacerlo process._tickCallback(o incluso% RunMicrotick): romperá aleatoriamente las cosas en su código. Intenté desesperadamente que funcionara (para temporizadores falsos en funciones asíncronas, principalmente) y nunca fue lo suficientemente estable desde el lado del Nodo. Dejé de trabajar en ello. La API de espejo de depuración V8 es completamente apropiada aquí.
Benjamin Gruenbaum
Y .. DeprecationWarning: DebugContext has been deprecated and will be removed in a future version.:( Parece que V8 lo eliminó
Benjamin Gruenbaum
Sin embargo, nosotros (Nodo) podemos pedirle a V8 una API o exponer una API para ver el estado de una promesa directamente; si abre un problema en github.com/nodejs/promise-use-cases , lo mencionaré felizmente con V8
Benjamin Gruenbaum
1
Un comentario más adelante en este tema reveló que ya parece existir una API: process.binding('util').getPromiseDetails( promise )devoluciones [ 0, ]por pendientes, [ 1, value ]por cumplidas y [ 2, value ]por rechazadas.
Matthijs
3

Advertencia: este método utiliza elementos internos de Node.js no documentados y se puede cambiar sin previo aviso.

En Node puede determinar sincrónicamente el estado de una promesa utilizando process.binding('util').getPromiseDetails(/* promise */);.

Esto devolverá:

[0, ] por pendiente,

[1, /* value */] por cumplido, o

[2, /* value */] por rechazado.

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

Envolviendo esto en una función auxiliar:

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

getStatus(pending); // pending
getStatus(fulfilled); // fulfilled
getStatus(rejected); // rejected
Scott Rudiger
fuente
No parece funcionar desde adentro jest(que es el único lugar en el que estoy interesado, de verdad). La función existe, pero siempre parece regresar undefined. ¿Cómo averiguo lo que está mal?
Adam Barnes
Hmm, lo recuerdo trabajando dentro mocha; jestaunque nunca lo probé . ¿Quizás comience una nueva pregunta que vincule aquí e incluya su versión Node.js y su jestversión?
Scott Rudiger
Desafortunadamente, ya no es algo que me interese mucho. Básicamente, estaba buscando evaluar la cordura de mi dispositivo de resolución / rechazo manual Promiseque solo estaba usando para probar cosas que deberían estar sucediendo mientras Promiseestá pendiente, pero pensé que mientras lo que escribí funciona, entonces no hay necesidad de probar eso Además de lo que se basa en ello.
Adam Barnes
2

lo que puede hacer es usar una variable para almacenar el estado, establecer manualmente el estado en esa variable y verificar esa 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

Por supuesto, esto significa que debe tener acceso al código original de la promesa. Si no lo hace, entonces puede hacer:

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

Mi solución es más codificación, pero creo que probablemente no tenga que hacer esto para cada promesa que use.

Gran nombre
fuente
2

Puede agregar un método a Promise.prototype. Se parece a esto:

Editado: la primera solución no funciona correctamente, como la mayoría de las respuestas aquí. Devuelve "pendiente" hasta que se invoca la función asincrónica ".then", lo que no ocurre de inmediato. (Lo mismo se trata de soluciones que usan Promise.race). Mi segunda solución resuelve este 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;
    };
}

Puedes usarlo en cualquier Promesa. Por ejemplo:

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

Segunda (y correcta) solución:

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

Y úsalo:

Aviso : en esta solución no tiene que usar el operador "nuevo".

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

Aquí hay una versión es6 más desarrollada de QueryablePromise, que permite encadenar y capturar después de la primera resolución e inmediatamente resolver o rechazar para mantener la API coherente con la Promesa 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
fuente
1

awaituso para la respuesta de @ jib , con prototipos idiomáticos.

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

tenga en cuenta que esta función asíncrona se ejecuta "casi" inmediatamente como una función sincronizada (o en realidad puede ser instantáneamente).

Valen
fuente
1

2019:

La forma simple de hacer eso, como sé thenable, es un envoltorio súper delgado para promesa o cualquier trabajo así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
fuente
1

Puede extendla clase promesa de crear un nuevo consultable clase de Promise.

Puede crear su propia subclase, por ejemplo QueryablePromise, heredando de la Promiseclase disponible de forma nativa , cuyas instancias tendrían una statuspropiedad disponible que puede usar para consultar el estado de los objetos de promesa de forma sincrónica . Una implementación de la misma se puede ver a continuación o consulte esto para una mejor explicación.

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
fuente
Desafortunadamente, ninguna API existente devolverá esta nueva clase. ¿Cómo te imaginas que la gente usa esto?
foque
@jib Gracias por tu respuesta. ¿Qué quiere decir que ninguna API devolvería esta clase? :(
UtkarshPramodGupta
Ninguna API existente lo devolverá, ya que tendrían que escribirse para devolverlo, ¿verdad? Por ejemplo, si lo llamo fetch, me devolverá una promesa nativa. ¿Cómo ayudaría tu clase con eso?
foque
Bueno, ¿no podemos envolver esa llamada de búsqueda en nuestro nuevo QuerablePromise como const queryableFetch = new QueryablePromise((resolve, reject) => {fetch(/.../).then((data) => resolve(data)) }):? O, ¿hay algún problema con eso? : /
UtkarshPramodGupta
Eso debería funcionar, simplemente no lo olvide , err => reject(err)como un segundo argumento theno no propagará los errores correctamente (entre las razones por las que se considera el antipatrón del constructor de promesas ). Sin embargo, no es realmente sincrónico (por ejemplo, no detectará una promesa ya resuelta), pero tal vez sea útil en los casos en que no controle a la persona que llama y la respuesta sea necesaria de inmediato.
foque
1

Hay otra forma elegante y hacky de verificar si una promesa aún está pendiente simplemente convirtiendo todo el objeto en una cadena y verificándolo con la ayuda de una inspección como esta:util.inspect(myPromise).includes("pending") .

Probado en Node.js 8,9,10,11,12,13

Aquí hay un ejemplo 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
fuente
0

Si usa ES7 experimental, puede usar async para ajustar fácilmente la promesa que desea escuchar.

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
fuente
0

He escrito un pequeño paquete npm, promise-value, que proporciona un contenedor de promesas con una resolvedbandera:

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

También le da acceso sincrónico al valor prometido (o error). Esto no altera el objeto Promise en sí mismo, siguiendo el ajuste en lugar de extender el patrón.

Daniel Winterstein
fuente
0

Esta es una pregunta anterior, pero estaba tratando de hacer algo similar. Necesito mantener a n trabajadores trabajando. Están estructurados en una promesa. Necesito escanear y ver si están resueltos, rechazados o aún pendientes. Si se resuelve, necesito el valor, si se rechaza hacer algo para corregir el problema o pendiente. Si se resuelve o rechaza, necesito comenzar otra tarea para continuar. No puedo encontrar una manera de hacerlo con Promise.all o Promise.race, ya que sigo trabajando las promesas en una matriz y no puedo encontrar la manera de eliminarlas. Entonces creo un trabajador que hace el truco

Necesito una función de generador de promesas que devuelva una promesa que se resuelva o rechace según sea necesario. Es invocado por una función que configura el marco para saber qué está haciendo la promesa.

En el siguiente código, el generador simplemente devuelve una promesa basada en setTimeout.

Aquí 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 devuelve un objeto que contiene la promesa y su estado y valor devuelto.

El siguiente código ejecuta un ciclo que prueba el estado y crea nuevos trabajadores para mantenerlo en 3 trabajadores en ejecución.

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

Probado en node.js

Por cierto, no tanto en esta respuesta, sino en otros sobre temas similares, ODIO cuando alguien dice "no entiendes" o "no funciona así". Por lo general, supongo que el interlocutor sabe lo que quiere. Sugerir una mejor manera es genial. Una explicación paciente de cómo funcionan las promesas también sería buena.

Charles Bisbee
fuente
-1

Encontré que esta solución es simple y me permite continuar usando promesas nativas, pero agregar comprobaciones síncronas útiles. Tampoco tuve que sacar toda una biblioteca de promesas.

PRUEBA: esto solo funciona si hay algún tipo de interrupción en el hilo de ejecución actual para permitir que las promesas se ejecuten ANTES de verificar las construcciones síncronas. Eso hace que esto tenga una utilidad más limitada de lo que inicialmente pensé, aunque sigue siendo útil para mi caso de uso (gracias Benjamin Gruenbaum por señalar esto)

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

Desde https://ourcodeworld.com/articles/read/317/how-to-check-if-a-javascript-promise-has-been-fulfilled-rejected-or-resolved que basaron su respuesta en ¿Hay alguna manera de saber si una promesa de ES6 se cumple / rechaza / resuelve?

Akrikos
fuente
Como se agregó en su comentario sobre mi respuesta, esto es completamente incorrecto: eso no le permite inspeccionar sincrónicamente el estado de una promesa. Por ejemplo, MakeQueryablePromise(Promise.resolve(3)).isResolvedes falso, pero la promesa está obviamente resuelta. Sin mencionar que la respuesta también está usando el término "resuelto" y "cumplido" incorrectamente. Para hacer esa respuesta, simplemente puede agregar un .thencontrolador usted mismo, que omite por completo el punto de inspección sincrónica.
Benjamin Gruenbaum
Veo lo que estás diciendo y haces un buen punto. La naturaleza de un solo subproceso de JS se está interponiendo, ¿no? Debe interrumpir la ejecución actual para que la promesa se marque como resuelta. let wrappedPromise = MakeQueryablePromise(Promise.resolve(3)); setTimeout(function() {console.log(wrappedPromise.isFulfilled())}, 1);Que mientras lo hagas, esto funciona bien. Pero tienes que entender ese hecho para que esto sea útil. Actualizaré la descripción con esa advertencia. También estoy de acuerdo en que el nombre de la función podría ser mejor / más idiomático.
Akrikos
Pero en ese momento, podrías thencumplir la promesa original y lograr lo mismo, ya que de todos modos es asíncrono. Hay una manera con process.binding('util').getPromiseDetailsque parece funcionar, pero está utilizando una API privada
Benjamin Gruenbaum
Es desagradable tener que hacerlo todo el tiempo y hace que el código sea mucho más difícil de entender. Especialmente cuando todo lo que me importa es si la promesa ha sido rechazada o no, por lo que mis opciones son almacenar ese estado en otro lugar o hacer algo como esto. Admito que no leí las otras soluciones aquí antes de publicar las mías. Este problema es más difícil de lo que pensé al principio.
Akrikos