Подтвердить что ты не робот

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

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

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

Из спецификация, Promise может быть одним из:

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

У меня есть вариант использования, когда я хочу синхронно допросить Promise и определить:

  • Провозглашено обещание?

  • если это так, разрешено ли обещание?

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

Этот вопрос специально посвящен синхронному опросу состояния Promise. Как я могу достичь этого?

4b9b3361

Ответ 1

Нет такого синхронного API проверки для встроенного JavaScript promises. Это невозможно сделать с помощью native promises. В спецификации не указан такой метод.

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

Ответ 2

enter image description here

Обещание-статус-асинхронный делает свое дело. Это асинхронное, но он не использует then ждать обещания решить.

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

Ответ 3

Нет, нет sync API, но здесь моя версия async 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

Ответ 4

Вы можете сделать гонку с Promise.resolve
Это не синхронно, а происходит сейчас

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

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

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

promateate make.race и .then: Уровень 2

Ответ 5

Вы можете использовать (уродливый) взлом в Node.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
}

Ответ 6

Вы можете обернуть promises таким образом

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

Ответ 7

Обновлено: 2019

Bluebird.js предлагает это: http://bluebirdjs.com/docs/api/isfulfilled.html

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

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

Поскольку JavaScript является однопоточным, трудно найти достаточно распространенный вариант использования, чтобы оправдать включение этого в спецификацию. Лучшее место, чтобы узнать, выполнено ли обещание, находится в .then(). Проверка выполнения Обещания создаст цикл опроса, который, скорее всего, будет неправильным направлением.

async/await - хорошая конструкция, если вы хотите синхронно рассуждать асинхронный код.

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

Еще один полезный вызов - 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"]

Когда я впервые добрался до этого ответа, это был тот вариант использования, который я искал.

Ответ 8

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

Ответ 9

Предупреждение: этот метод использует недокументированные внутренние компоненты Node.js и может быть изменен без предупреждения.

В Node вы можете синхронно определять состояние обещания, используя process.binding('util').getPromiseDetails(/* promise */); ,

Это вернет:

[0, ] в ожидании,

[1,/* value */] для выполненного или

[2,/* value */] для отклоненных.

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

Оборачиваем это в вспомогательную функцию:

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

getStatus(pending); // pending
getStatus(fulfilled); // fulfilled
getStatus(rejected); // rejected

Ответ 10

в узле, скажем, недокументированное внутреннее 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> ]

Ответ 11

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

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 code
somePromise.then(function(){
  state = 'resolved';
}, function() {
  state = 'rejected';
})

console.log(state);//check the promise state somewhere else in the code

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

Ответ 12

Как и в Node.js версии 8, теперь вы можете использовать пакет wise-inspection, чтобы синхронно обследовать собственный promises ( без каких-либо опасных хаков).

Ответ 13

Вы можете добавить метод к Promise.prototype. Это выглядит так:

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

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

И используйте его:

Примечание. В этом решении вам не нужно использовать "новый" оператор.

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

Ответ 14

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

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>

Ответ 15

Если вы используете экспериментатор ES7, вы можете использовать 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;
}

Ответ 16

Я написал небольшой пакет npm, стоимость обещания, которая предоставляет оболочку обещания с флагом resolved:

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

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

Ответ 17

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

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

В приведенном ниже коде генератор просто возвращает обещание, основанное на 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();

Протестировано в node.js

Кстати, не в этом ответе так много, но в других на подобные темы, я НЕНАВИЖУ это, когда кто-то говорит, что "вы не понимаете" или "что не так, как это работает", я обычно предполагаю, что спрашивающий знает, чего они хотят. Предлагать лучший способ - это здорово. Терпеливое объяснение того, как работают обещания, также было бы хорошо.

Ответ 18

await ответа @jib с идиоматическим прототипированием.

Object.defineProperty(Promise.prototype, "state", {
    get: function(){
        const o = {};
        return Promise.race([this, o]).then(
            v => v === o ? "pending" : "resolved",
            () => "rejected");
    }
});

// usage:
(async () => {
    console.log(await <Your Promise>.state);
    console.log(await Promise.resolve(2).state);  // "resolved"
    console.log(await Promise.reject(0).state);   // "rejected"
    console.log(await new Promise(()=>{}).state); // "pending"
});

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

Ответ 19

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

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

/**
 * This function allow you to modify a JS Promise by adding some status properties.
 * Based on: http://stackoverflow.com/info/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);

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