Як я можу синхронно визначити стан JavaScript Promise?


149

У мене є чиста JavaScript Обіцянка (вбудована реалізація або полі-заливка):

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

З специфікації , Обіцянка може бути одним із:

  • 'осілий' і 'вирішений'
  • "осілий" і "відхилений"
  • "в очікуванні"

У мене є випадок використання, коли я хочу запитати Обіцяння синхронно та визначити:

  • чи обіцяна Обіцянка?

  • якщо так, чи вирішено Обіцянку?

Я знаю, що можу використовувати #then()для планування роботи, яку слід виконати асинхронно після стану Обіцяння змін. Я НЕ запитую, як це зробити.

Це питання стосується синхронного допиту держави Обіцянки . Як я можу цього досягти?


6
встановіть властивість на обіцянку, яку можна побачити ззовні, і використовуйте тоді () для зміни властивості.
dandavis

@jokeyrhyme FWIW, джерело v8 code.google.com/p/v8/source/browse/branches/bleeding_edge/src / ... см var promiseStatus = NEW_PRIVATE("Promise#status");, PromiseSetфункція уSET_PRIVATE(promise, promiseStatus, status);
guest271314

Ось ми їдемо: esdiscuss.org/topic/…
jokeyrhyme

Дивно здається, що якщо ви робите const a = Promise.resolve ('baz'); console.log (a); і подивіться в консоль Chrome, ви побачите Promise {[[PromiseStatus]]: "вирішено", [[PromiseValue]]: "baz"} протокол: Promise [[PromiseStatus]]: "вирішено" [[PromiseValue]]: "baz "І люди стверджують, що цього зробити не можна. Як Chrome це робить? (робив це в Plunker з кутовим plnkr.co/edit/IPIWgLJKQStI5ubXmcsF
JGFMK

Використання вузла v11.12.0 console.log покаже стан обіцянки. EG console.log(Promise.new((resolve, reject) => {})=>Promise { <pending> }
Пульзе

Відповіді:


77

Не існує такого API синхронної перевірки для власних обіцянок JavaScript. Зробити це з рідними обіцянками неможливо. У специфікації не вказаний такий спосіб.

Бібліотеки Userland можуть це зробити, і якщо ви орієнтуєтесь на певний двигун (наприклад, v8) і маєте доступ до коду платформи (тобто ви можете писати код в ядрі ), тоді ви можете використовувати конкретні інструменти (як приватні символи) для досягнення цього . Це дуже специфічно, але не в користувальницькій країні.


4
Примітка. Я чесно вважаю, що випадки використання для синхронної перевірки є рідкісними і дуже рідкісними, якщо ви ділитесь вашим конкретним випадком використання в новому запитанні, яке запитує, як цього досягти без синхронної перевірки - я дам відповідь на нього, якщо хтось не стане побили мене до цього :)
Бенджамін Грюнбаум

4
Навіть якщо випадки використання є рідкісними, яка шкода може включати щось подібне? Мені знадобиться перевірка статусу, як це, щоб побачити, чи попередня робота була закінчена і чи можу я подати запит на іншу роботу. І я не можу просто встановити зовнішню змінну, тому що об’єкт має можливість змінити власників без попереднього повідомлення. Що ще більше дратує те, що я можу SEE Node.js отримати доступ до цієї інформації, тому що вона мені показує її, коли я її перевіряю, але немає способу потрапити на неї, крім розбору рядків ??
Tustin2121

9
Тому ми повинні відкинути рідні обіцянки, оскільки вони непрактичні і завжди використовувати блакитну птицю. Відмінна новина! Як я пропоную власні обіцянки застаріти та викинути з двигуна вузла?
user619271

1
Багато речей ми мали .anyзамість цього подумати і помилитися, оскільки Марк наполягав. По-перше, Promise.race([])це обіцянка на віки (а не помилка), ти, як правило, хочеш першої успішної обіцянки, а не лише першої. У будь-якому разі, це не дуже важливо для поставленого питання - ОП запитала про синхронну перевірку, а не про .raceта багато її недоліків.
Бенджамін Грюенбаум

5
@Akrikos ця відповідь не дозволяє синхронно перевірити стан обіцянки - Наприклад MakeQueryablePromise(Promise.resolve(3)).isResolved, помилково, але обіцянка цілком очевидно вирішена. Не кажучи вже про те, що у відповіді використовується також термін «вирішено» і «виконано» неправильно. Для цього ви зможете просто додати .thenобробник самостійно - що повністю пропускає точку синхронної перевірки.
Бенджамін Грюнбаум

31

введіть тут опис зображення

обіцянка-статус-асинхрон робить свою справу. Це асинхронізація, але вона не використовує 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;
}

4
ОП запитала про те, як це зробити синхронно
Клесун

28

Ні, 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


Чи є конкретні міркування за цією конструкцією? Мені це здається зайвим. Наскільки я можу сказати, це працює однаково: Promise.race([ Promise.resolve(p).then(() => "fulfilled", () => "rejected"), Promise.resolve().then(() => "pending") ]); Хоча це здається мені більш безпечним: const t = {}; return Promise.race([p,t]).then(v => v === t ? "pending" : "fulfilled", () => "rejected") і уникає створення додаткових обіцянок, які зберігаються до тих пір, поки очікується вихідний p.
Маттайс

Дякую @Matthijs! Я спростив свою відповідь.
січня

16

Ви можете здійснити гонку за допомогою 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);
  });
}

Невеликий сценарій для тестування та розуміння їх значення асинхронно

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 (хром підтримує порядок)

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

Обещайте Держава зробіть .race і .then: 2 рівень


3
Замість цього 'a value that p should not return'використовуйте Symbol
програміст5000

1
@ programmer5000 Яка користь?
Моріц Шмітц проти Хюльста

2
@ MoritzSchmitzv.Hülst a Symbolбуло б унікальним значенням, тому вам ніколи не доведеться здогадуватися, що "значення [...] p не повинно повертатися". Однак посилання на конкретний об’єкт спрацювала б так само добре.
Скотт Рудігер

7

Ви можете використовувати (потворний) хак у 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
}

3
Я зварив його на Promise.prototype.isPending = function(){ return util.inspect(this).indexOf("<pending>")>-1; }
полісистемі

5
Це жахливо .
Джон Вайс

@JohnWeisz Що жахливе - це відсутність зворотної сумісності. Я намагаюся інтегрувати API-коди, що обіцяє, в кодову базу, яка передбачає, що все є синхронним. Це або робити щось жахливе, або переписувати величезні шматки коду. У будь-якому випадку я здійснюю злодіяння.
rath

4
просто використовуйтеprocess.binding('util').getPromiseDetails
amara

@ Tustin2121 Для деякої версії він вийде з ладу з чимось подібним Promise.resolve('<pending>').
користувач202729

7

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

я додав це, тому що його не було в жодній із існуючих відповідей, а для вузла це найкраща відповідь. легко знайти документи для цього в github.com/nodejs/node
amara

6

Оновлено: 2019 рік

Bluebird.js пропонує це: http://bluebirdjs.com/docs/api/isfulfilled.html

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

Якщо ви хочете створити власну обгортку, ось приємний блог про це.

Оскільки JavaScript є однопоточним, важко знайти досить поширений випадок використання, щоб виправдати введення цього в специфікацію. Найкраще знати, чи вирішено обіцянку, - in .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"]

Коли я вперше звернувся за цією відповіддю, це був той варіант використання, який я шукав.


5

Ви можете виконати свої обіцянки таким чином

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

5
Це вимагатиме від ОП отримати доступ до обіцянки в попередньому рубежі циклу подій . Оскільки .thenзавжди виконується асинхронно ОП, який хоче перевірити обіцянку в ту ж саму чергу , тут не вийде правильний результат. Примітка ОП запитала конкретно про синхронну перевірку і зазначила, що вони вже знають про асинхронну перевірку.
Бенджамін Грюенбаум

@BenjaminGruenbaum: Чи не з'являться значення за замовчуванням, якби код у тому ж "обороті" назвав його?
dandavis

Звичайно, вам доведеться виконати всі свої обіцянки під час створення. наприклад, всередині функцій, які їх створюють та повертають.
SpiderPig

3
Правильно, в цей момент вони вже не є справжніми обіцянками, ви можете також розширити їх так, як вони мають бути розширені, підкласифікацією, що дозволить вам зробити це елегантно замість мавпових властивостей патчу на об'єкті.
Бенджамін Груенбаум

Незалежно від того, чи поширюєте ви обіцянку так, як я показав, або підкласифікуючи, у кожному випадку вам все одно доведеться додати свою власну версію і передати.
SpiderPig

5

Дійсно, це дуже прикро, що цього базового функціоналу немає. Якщо ви використовуєте 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 };
};

Це дуже небезпечно process._tickCallback(або навіть звичайний% RunMicrotick) - це випадково порушить речі у вашому коді. Я відчайдушно намагався змусити його працювати (в основному для підроблених таймерів у функціях асинхронізації), і він ніколи не був достатньо стабільним з боку Вузла. Я якось відмовився працювати над цим. Тут повністю підходить API дзеркала для налагодження V8.
Бенджамін Груенбаум

І .. DeprecationWarning: DebugContext has been deprecated and will be removed in a future version.:( Схоже, V8 зняв його
Бенджамін Грюнбаум

Ми (Вузол) можемо повністю попросити V8 для API або викрити API для того, щоб безпосередньо переглянути стан обіцянки - якщо ви відкриєте проблему на github.com/nodejs/promise-use-cases, я з задоволенням підберу її до V8
Бенджамін Грюенбаум

1
Далі в коментарях до цієї теми було виявлено, що API вже існує: process.binding('util').getPromiseDetails( promise )повернення [ 0, ]за очікування, [ 1, value ]за виконане та [ 2, value ]для відхилення.
Matthijs

3

Caveat: Цей метод використовує недокументовані внутрішні протоколи 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

Здається, це не працює зсередини jest(це насправді єдине місце, коли я це цікавлю). Функція існує, але, здається, завжди повертається undefined. Як дізнатись, що не так?
Адам Барнс

Хм, я пам'ятаю, як це працювало всередині mocha; ніколи не пробував цього, jestхоча. Можливо, почніть нове запитання, що посилається тут і включить вашу версію Node.js, а також jestверсію?
Скотт

На жаль, вже не дуже мене цікавлять. Я в основному хотів перевірити обґрунтованість моєї розв’язуваності / відхилення вручну, Promiseяку я використовував лише для тестування матеріалів, які повинні тривати в процесі Promiseочікування, але я зрозумів, що працює те, що я написав, тоді не потрібно тестувати, що крім того, на що покладається.
Адам Барнс

2

що ви можете зробити, це використовувати змінну для зберігання стану, вручну встановити стан цієї змінної та перевірити цю змінну.

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

Моє рішення - це більше кодування, але я думаю, що вам, ймовірно, не доведеться цього робити за кожну обіцянку, яку ви використовуєте.



2

Ви можете додати метод до 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

1

Ось більш чітка версія версії 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>


1

awaitвикористання для відповіді @ jib з ідіоматичним прототипуванням.

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

зауважте, що ця функція асинхронізації виконується "майже" відразу як синхронізована функція (або насправді можливо миттєво).


1

2019:

Простий спосіб зробити це, як я знаю, - це thenableнадто тонка обгортка навколо обіцянок або будь-яка робота з асинхронізацією.

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

1

Ви можете за extendдопомогою класу Promise створити новий клас, який можна обібрати .

Ви можете створити власний підклас, скажімо QueryablePromise, успадкувавши від початково доступного Promiseкласу, екземпляри якого мали б у ньому statusвластивість, яку ви можете використовувати для синхронного запиту стану об’єктів обіцянки . Її реалізацію можна побачити нижче або посилатись на це для кращого пояснення.

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)


На жаль, жоден існуючий API не поверне цей новий клас. Як ти уявляєш, як люди використовують це?
гусек

@jib Дякуємо за вашу відповідь. Що ви маєте на увазі, що жоден API не поверне цей клас? :(
UtkarshPramodGupta

Жоден існуючий API не поверне його, тому що їх потрібно було б написати, щоб повернути його, правда? Наприклад, якщо я зателефоную, fetchце поверне рідну обіцянку. Як би ваш клас допомагав у цьому?
гусек

Що ж, чи не можемо ми просто запустити цей виклик у нашу нову QuerablePromise на зразок const queryableFetch = new QueryablePromise((resolve, reject) => {fetch(/.../).then((data) => resolve(data)) }):? Або з цим виникає проблема? : /
UtkarshPramodGupta

Це має спрацювати, просто не забувайте, , err => reject(err)як другий аргумент, thenабо він не поширюватиме помилки (серед причин це вважається анти-шаблоном конструктора обіцянок ). Це не справді синхронно (наприклад, не виявить вже розв'язану обіцянку), але, можливо, корисно у випадках, коли ви не керуєте користувачем, і відповідь потрібен негайно.
гусек

1

Існує ще один елегантний і хитрий спосіб перевірити, чи обіцянка все ще триває, просто перетворивши весь об'єкт у рядок і перевірити його за допомогою інспектування таким чином:util.inspect(myPromise).includes("pending") .

Тестовано на Node.js 8,9,10,11,12,13

Ось повний приклад

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

Результат:

true
true
false
false
false

0

Якщо ви використовуєте експериментальний 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;
}

0

Я написав невеликий пакет npm, value-value, який забезпечує обгортку обіцянки з resolvedпрапором:

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

Він також надає синхронний доступ до значення обіцянки (або помилки). Це не змінює сам об’єкт Обіця, слідуючи схемі обгортання, а не розширюючи шаблон.


0

Це питання старіше, але я намагався зробити щось подібне. Мені потрібно тримати російських робітників. Вони структуровані в обіцянку. Мені потрібно просканувати та побачити, чи вони вирішені, відхилені чи ще тривають. Якщо рішення вирішено, мені потрібно значення, якщо його відхиляють, зробити щось, щоб виправити проблему чи очікувати на розгляд. Якщо це вирішено чи відхилено, мені потрібно почати ще одне завдання, щоб продовжувати роботу. Я не можу знайти спосіб зробити це за допомогою 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

До речі, не в цій відповіді так багато, але в інших на подібні теми, я ненавиджу це, коли хтось каже "ти не розумієш" або "це не так, як це працює". Я загалом припускаю, що запитуючий знає, чого вони хочуть. Запропонувати кращий спосіб - це чудово. Терпляче пояснення того, як обіцяють роботу, також було б добре.


-1

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

CAVEAT: Це працює лише в тому випадку, якщо в поточному потоці виконання є якийсь розрив, який дозволяє обіцянкам виконати ДО ПЕРЕД перевірки синхронних конструкцій. Це робить це більш обмеженою корисністю, ніж я спочатку думав - все-таки корисно для мого випадку використання (спасибі Бенджаміну Груенбауму, що вказав на це)

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

З https://ourcodeworld.com/articles/read/317/how-to-check-if-a-javascript-promise-has-been-fulfilled-rejected-or-розгадується, що спирається на їх відповідь на те, чи є спосіб скажіть, чи виконано / відхилено / вирішено обіцянку ES6?


Як додано у вашому коментарі до моєї відповіді - це абсолютно неправильно: це не дозволяє синхронно перевіряти стан обіцянки - Наприклад MakeQueryablePromise(Promise.resolve(3)).isResolved, помилково, але обіцянка цілком очевидно вирішена. Не кажучи вже про те, що у відповіді використовується також термін «вирішено» і «виконано» неправильно. Для цього ви зможете просто додати .thenобробник самостійно - що повністю пропускає точку синхронної перевірки.
Бенджамін Грюенбаум

Я бачу, що ви говорите, і ви добре зазначаєте. Єдина різьбова природа JS стає на шляху, чи не так? Ви повинні зробити перерву в поточному виконанні, щоб обіцянка була позначена як вирішена. let wrappedPromise = MakeQueryablePromise(Promise.resolve(3)); setTimeout(function() {console.log(wrappedPromise.isFulfilled())}, 1);Який поки ти це робиш, це чудово працює. Але ви повинні розуміти цей факт, щоб це було корисно. Я оновлю опис із цим застереженням. Я також погоджуюся, що іменування функції може бути кращим / ідіоматичним.
Акрикос

Але в цей момент ви могли просто просто thenпообіцяти і виконати те саме, оскільки це все одно асинхронно. Існує спосіб, process.binding('util').getPromiseDetailsщо, здається, працює, але він використовує приватний API
Бенджамін Груенбаум,

Це неприємно постійно робити це і робить код набагато складніше зрозуміти. Особливо, коли мені все одно, якщо обіцянку було відхилено чи ні - тож мої варіанти - або зберігати цей стан десь в іншому місці, або робити щось подібне. Зізнаюся, я не прочитав тут ретельно інших рішень, перш ніж публікувати свої власні - вибачте за це. Ця проблема є більш жорсткою, ніж я спочатку думав.
Акрикос
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.