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


149

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

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

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

  • «решен» и «решен»
  • «урегулирован» и «отклонен»
  • «В ожидании»

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

  • Посыл обещан?

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

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

Этот вопрос конкретно о синхронном опросе состояния Обещания . Как мне этого добиться?


6
установите свойство в обещании, которое можно увидеть снаружи, и используйте then () для изменения свойства.
Дандавис

@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 (а); и загляните в консоль Chrome, вы увидите Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "baz"} proto: Promise [[PromiseStatus]]: "resolved" [[PromiseValue]]: "baz "и люди утверждают, что это не может быть сделано. Как Chrome делает это? (делал это в Plunker с Angular plnkr.co/edit/IPIWgLJKQStI5ubXmcsF
JGFMK

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

Ответы:


77

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

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


4
Примечание: я искренне верю, что случаи использования для синхронной проверки немногочисленны и очень редки, если вы поделитесь своим конкретным примером использования в новом вопросе о том, как его достичь без синхронной проверки - я дам ответ на него, если кто-то не будет обыграй меня :)
Бенджамин Грюнбаум

4
Даже если случаи использования редки, какой вред будет включать что-то подобное? Мне нужна такая проверка статуса, чтобы увидеть, была ли завершена предыдущая работа и могу ли я запросить другую работу. И я не могу просто установить внешнюю переменную, потому что объект может менять владельцев без уведомления. Что еще раздражает, я могу ВИДЕТЬ, что 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

Спасибо @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 (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

Обещание государства сделать .race и .then: уровень 2


3
Вместо того 'a value that p should not return', чтобы использовать Symbol
programmer5000

1
@ programmer5000 В чем выгода?
Мориц Шмитц против Хюлста

2
@ MoritzSchmitzv.Hülst 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; }
Tustin2121

5
Это ужасающее .
Джон Вайс

@JohnWeisz Что ужасно, так это отсутствие обратной совместимости. Я пытаюсь интегрировать обещающий API в кодовую базу, которая предполагает, что все синхронно. Он либо делает что-то ужасное, либо переписывает огромные куски кода. В любом случае я совершаю злодеяние.
Рат

4
просто использоватьprocess.binding('util').getPromiseDetails
амара

@ Tustin2121 Для какой-то версии он потерпит неудачу с чем-то вроде Promise.resolve('<pending>').
user202729

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 является однопоточным, трудно найти достаточно распространенный вариант использования, чтобы оправдать включение этого в спецификацию. Лучшее место, чтобы узнать, выполнено ли обещание, находится в .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
Для этого OP должен получить доступ к обещанию в предыдущем цикле цикла событий . Так как .thenвсегда выполняется асинхронно, OP, который хочет проверить обещание в один и тот же ход , не получит правильный результат здесь. Примечание ОП спрашивал конкретно о синхронной проверке и упоминал, что они уже знают об асинхронной проверке.
Бенджамин Грюнбаум

@BenjaminGruenbaum: не появятся ли значения по умолчанию, если код в том же «повороте» вызвал его?
Дандавис

Конечно, вы должны будете обернуть все свои обещания во время создания. например, внутри функций, которые создают и возвращают их.
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

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

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

Примечание : в этом решении вам не нужно использовать оператор «new».

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

1

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

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 для создания нового запрашиваемого класса 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 не вернет этот класс? :(
УткаршПрамодГупта

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

Ну, мы не можем просто обернуть , что принести вызов в нашем новом QuerablePromise как: const queryableFetch = new QueryablePromise((resolve, reject) => {fetch(/.../).then((data) => resolve(data)) })? Или есть проблема с этим? : /
УткаршПрамодГупта

Это должно сработать, просто не забывайте , err => reject(err)в качестве второго аргумента, thenиначе оно не будет правильно распространять ошибки (среди причин, по которым это считается анти-паттерном конструктора обещаний ). Хотя это не совсем синхронно (например, не обнаружит уже выполненное обещание), но, возможно, полезно в тех случаях, когда вы не контролируете звонящего и ответ требуется немедленно.
гусек

1

Там еще один элегантный и Hacky способ проверки , если обещание еще находится на рассмотрении только путем преобразования всего объекта в строку и проверить его с помощью инспектировать , как это: 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, promise-value, который предоставляет упаковщик обещаний с resolvedфлагом:

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

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


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-resolved, который основывает свой ответ на « Есть ли способ скажите, выполнено ли обещание 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.