Как я могу синхронно определить состояние обещания JavaScript?


У меня есть чистое обещание JavaScript (встроенная реализация или Поли-заполнение):

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

С спецификация обещание может быть одним из следующих:

  • "улажено" и "решено"
  • "улажено" и "отвергнуто"
  • 'в ожидании'

у меня есть прецедент, когда я хочу допросить обещание синхронно и определить:

  • - это обещание договорились?

  • если да, то обещание выполнено?

Я знаю, что я могу использовать #then() чтобы запланировать работу, которая будет выполняться асинхронно после изменения состояния обещания. Я не спрашиваю, как это сделать.

этот вопрос конкретно о синхронный допрос состояния обещания. Как я могу этого достичь?

15 80

15 ответов:

такого синхронного API проверки не существует для собственных обещаний JavaScript. это невозможно сделать с родными обещаниями. В спецификации такой метод не указан.

библиотеки Userland могут это сделать, и если вы нацелены на определенный движок( например, v8) и имеете доступ к платформа код (то есть, вы можете писать код в базовый), то вы можете использовать специальные инструменты (например, закрытые символы) для достижения этой цели. Это очень конкретный хотя и не в пространстве пользователя.

вы можете сделать гонку с обещанием.решите
Это не синхронно, но происходит сейчас

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

немного скриптов для тестирования и понимания их значения асинхронно

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

результаты с задержкой(0) (комментарий в то время как в задержке)

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

и результаты этого теста с firefox (chrome держать порядок)

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 сделать .раса и. тогда : Уровень 2

нет, нет синхронизации API, но вот моя версия асинхронного promiseState (С помощью @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

вы можете использовать (уродливый) Хак в узле.js, пока не будет предложен собственный метод:

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
}

вы можете обернуть свои обещания таким образом

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

Это действительно очень раздражает, что эта основная функциональность отсутствует. Если вы используете узел.js тогда я знаю два обходных пути, ни один из них не очень красивый. Оба фрагмента ниже реализуют один и тот же 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' }

кажется, нет никакого способа отличить последние два состояния обещания, используя любой трюк.

1. Используйте API отладки V8

Это тот же трюк, что util.inspect использует.

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. Синхронно запустить микрозадачи

Это позволяет избежать API отладки, но имеет некоторую пугающую семантику, вызывая все ожидающие микрозадачи и process.nextTick обратные вызовы, которые будут выполняться синхронно. Он также имеет побочный эффект предотвращения ошибки "необработанного отклонения обещания", которая когда-либо срабатывала для проверенного обещания.

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

Синяя птица.js предлагает следующее:http://bluebirdjs.com/docs/api/isfulfilled.html

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

что вы можете сделать, это использовать переменную, чтобы сохранить, вручную установить состояние этой переменной, и проверять эту переменную.

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

конечно, это означает, что вы должны иметь доступ к исходному коду обещание. Если вы этого не сделаете, то вы можете сделать:

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

мое решение-это больше кодирования, но я думаю, что вам, вероятно, не придется делать это для каждого обещания, которое вы используете.

по состоянию на узел.js версии 8, Теперь вы можете использовать мудрый-инспекции пакет для синхронной проверки родных обещаний (без каких-либо опасных хаков).

вы можете добавить метод, чтобы обещать.прототип. Выглядит это так:

редактировать: первое решение не работает, как и большинство ответов здесь. Он возвращает "ожидание" до асинхронной функции".затем " вызывается, что происходит не сразу. (То же самое касается решений с использованием Promise.гонка.) Мое второе решение решает эту проблему.

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

вы можете использовать его на любые обещания. Например:

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

второй (и правильное) решение:

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

и использовать его:

обратите внимание: в этом решении вам не нужно использовать оператор "new".

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

в узел, скажем process.binding('util').getPromiseDetails(promise)

Если вы используете ES7 experimental, вы можете использовать async, чтобы легко обернуть обещание, которое вы хотите прослушать.

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

Я написал небольшой пакет npm, promise-value, который предоставляет обертку promise с resolved флаг:

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

Он также дает синхронный доступ к значению обещания (или ошибке). Это не изменяет сам объект Promise, следуя шаблону wrap, а не extend.

Это старый вопрос, но я пытался сделать что-то подобное. Мне нужно, чтобы держать работников собирается. Они структурированы в обещание. Мне нужно сканировать и посмотреть, разрешены ли они, отклонены или все еще ожидают. Если решено, мне нужно значение, если отклонено сделать что-то, чтобы исправить проблему или в ожидании. Если решено или отклонено, мне нужно начать другую задачу, чтобы продолжить работу. Я не могу выяснить способ сделать это.все или обещаю.гонка, как я продолжаю работать обещания в массиве и не может найти нет способ их удаления. Поэтому я создаю работника, который делает трюк

мне нужна функция генератора обещаний, которая возвращает обещание, которое разрешает или отклоняет по мере необходимости. Он вызывается функцией, которая устанавливает структуру, чтобы знать, что делает обещание.

в коде ниже генератор просто возвращает обещание на основе setTimeout.

здесь

//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 возвращает объект, содержащий обещание и его состояние и возвращается значение.

следующий код запускает цикл, который проверяет состояние и создает новых работников, чтобы сохранить его на 3 работающих рабочих.

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

проверено в узле.js

Я нашел это решение простым и позволяю мне продолжать использовать собственные обещания, но добавлять полезные синхронные проверки. Мне также не нужно было тянуть всю библиотеку обещаний.

предостережение: это работает только в том случае, если есть какой-то перерыв в текущем потоке выполнения, чтобы разрешить выполнение обещаний перед проверкой синхронных конструкций. Это делает это более ограниченной полезностью, чем я изначально думал - все еще полезно для моего случая использования (спасибо Бенджамин Gruenbaum для указания на это)

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

From https://ourcodeworld.com/articles/read/317/how-to-check-if-a-javascript-promise-has-been-fulfilled-rejected-or-resolved который основывал свой ответ на есть ли способ узнать, выполнено ли обещание ES6 / отклонено / разрешено?