如何同步确定JavaScript Promise的状态?


149

我有一个纯JavaScript Promise(内置实现或poly-fill):

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

根据规范,Promise可以是以下之一:

  • “解决”和“解决”
  • “解决”和“拒绝”
  • “待定”

我有一个用例,希望同步查询Promise并确定:

  • 承诺解决了吗?

  • 如果是这样,承诺是否得到解决?

我知道我可以#then()用来安排Promise更改状态后异步执行的工作。我不是问怎么做。

这个问题特别是关于对Promise状态的同步询问。我该如何实现?


6
在promise上设置一个可以从外部看到的属性,然后使用then()更改属性。
dandavis,2015年

@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


如果执行const a = Promise.resolve('baz');似乎很奇怪。console.log(a); 并在Chrome控制台中查看,您会看到Promise {[[PromiseStatus]]:“ resolved”,[[PromiseValue]]:“ baz”} proto:Promise [[PromiseStatus]]:“ resolved” [[PromiseValue]]:“ baz人们认为这是不可能的。Chrome的运作方式如何?(通过Angular plnkr.co/edit/IPIWgLJKQStI5ubXmcsF
JGFMK

使用节点v11.12.0 console.log将显示承诺状态。EG console.log(Promise.new((resolve, reject) => {})=>Promise { <pending> }
Puhlze '19

Answers:


77

没有用于本机JavaScript承诺的此类同步检查API。凭本地承诺是不可能做到这一点的。规范未指定这种方法。

Userland库可以做到这一点,如果您要针对特定​​的引擎(例如v8)并有权访问平台代码(即可以在core中编写代码),则可以使用特定的工具(例如私有符号)来实现这一目标。 。这是非常具体的,但不是在用户领域。


4
注意:如果您在一个新的问题中共享具体的用例询问如何在不进行同步检查的情况下实现它,那么我真的相信同步检查的用例很少而且很少见。打败我:)
本杰明·格伦鲍姆

4
即使用例很少,包括这样的内容也会带来什么危害呢?我需要这样的状态检查,以查看上一个作业是否已完成以及是否可以请求其他作业。而且我不能只设置一个外部变量,因为该对象有可能在不通知的情况下更改所有者。更令人烦恼的是,我可以看到Node.js可以访问此信息,因为它在我检查时会向我显示,但是除了解析字符串外,没有其他方法可以使用?
Tustin2121 '16

9
因此,我们必须放弃原生承诺,因为它们不切实际,并且始终使用蓝鸟。好消息!我如何提出本机的诺言,使其过时并退出节点引擎?
user619271 '16

1
在很多事情上,我们本来应该猜测.any并且犯了一个错误,因为马克坚持。对于一个Promise.race([])是永远待定的承诺(而不是错误),您通常需要第一个成功的承诺,而不仅仅是第一个承诺。无论如何,这与所问的问题并没有真正的关系-OP询问了同步检查,而不是有关.race它的许多缺点。
本杰明·格林鲍姆

5
@Akrikos的答案不能让您同步检查承诺的状态-例如MakeQueryablePromise(Promise.resolve(3)).isResolved错误,但承诺显然得到了解决。更不用说答案也错误地使用了术语“已解决”和“已实现”。为此,您是否可以.then自己添加一个处理程序-完全错过了同步检查的要点。
本杰明·格林鲍姆

31

在此处输入图片说明

promise-status-async可以解决问题。它是异步的,但是它并不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
OP询问了如何同步进行操作
Klesun '19

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

具有delay(0)的结果(注释while延迟)

00001 end of promises
00001 end of script
00001 Level 1 : One
00001 Level 1 : Three
00001 p1 Is Resolved One
00001 p2 Is Pending undefined
00001 p3 Is Rejected Three
00001 Level 2 : One
00001 Level 2 : Three
00001 delayed L3 : Three
00002 Level 3 : One
00002 Level 3 : Three
00006 timeOut 0 ms
00100 timeOut 100 ms
00100 Level 1 : Two
00100 Level 2 : Two
00101 Level 3 : Two
00189 timeOut 200 ms

和使用Firefox进行测试的结果(chrome保持顺序)

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

promiseState使.race和.then:级别2


3
取而代之的是'a value that p should not return',使用符号
程序员

1
@ programmer5000有什么好处?
Moritz Schmitz诉Hülst18年

2
@MoritzSchmitzv.Hülsta Symbol将是唯一的值,因此您不必猜测“值p不应返回”。但是,对特定对象的引用也将起作用。
Scott Rudiger

7

在提供本机方法之前,您可以在Node.js中使用(丑陋的)hack:

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

5
那太可怕了
约翰·魏茨

@JohnWeisz可怕的是缺少向后兼容性。我正在尝试将一个有前途的API集成到一个假定一切都是同步的代码库中。它要么做着可怕的事情,要么重写了大量的代码。无论哪种方式,我都犯下了暴行。
rath

4
只需使用process.binding('util').getPromiseDetails
amara

@ 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()。测试Promise是否已满,将创建一个轮询循环,这很可能是错误的方向。

如果您想同步推理异步代码,那么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 在事件循环的上一回合中访问promise 。由于.then总是异步执行OP,想要同时检查承诺的OP 这里将无法获得正确的结果。注意OP特别询问了同步检查,并提到他们已经了解异步检查。
本杰明·格伦鲍姆

@BenjaminGruenbaum:如果在同一“转弯”处调用代码会不会出现默认值?
dandavis,2015年

当然,您必须在创建时包装所有诺言。例如在创建和返回它们的函数中。
SpiderPig 2015年

3
正确,此时它们不再是真正的原生承诺,您也可以通过子类扩展它们的扩展方式,这将使您可以优雅地做到这一点,而不必在对象上进行猴子修补属性。
本杰明·格林鲍姆

无论您是按照我的方式还是通过子类扩展承诺,在每种情况下,您仍然都必须添加自己的then版本并进行捕获。
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.使用V8调试API

这是使用相同的把戏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)-它会随机破坏代码中的内容。我拼命尝试使其工作(主要用于异步函数中的伪计时器),并且从节点角度来看,它永远都不够稳定。我有点放弃了。V8调试镜像API在这里完全合适。
本杰明·格林鲍姆

和.. DeprecationWarning: DebugContext has been deprecated and will be removed in a future version.:(看起来V8删除了它
Benjamin Gruenbaum

我们(节点)可以完全向V8索要API或直接公开API以查看诺言的状态-如果您在github.com/nodejs/promise-use-cases中打开问题,我会很乐意在V8中提出它
本杰明·格伦鲍姆(Benjamin Gruenbaum)

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版本?
Scott Rudiger

不幸的是,我不再对很多东西感兴趣。我基本上是想进行手动可解析/可拒绝的健全性测试Promise,而我只是用来测试当a 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

当然,这意味着您必须有权访问promise的原始代码。如果不这样做,则可以执行以下操作:

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

您可以在任何Promise上使用它。举个例子:

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

这是Query6Promise的一个充实的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,最简单的方法是在promise或任何异步作业周围使用超薄包装。

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可用的属性,您可以使用该属性来同步查询promise对象的状态。可以在下面看到它的实现,也可以参考以获得更好的解释。

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)作为第二个arg,then否则它不会正确传播错误(在被认为是promise构造函数反模式的原因中)。尽管它不是真正同步的(例如,不会检测到已经解决的承诺),但在您不控制呼叫者并且立即需要答案的情况下可能很有用。
三角帆

1

还有一种优雅的方法,可以通过将整个对象转换为字符串并借助诸如以下的inspect进行检查来检查promise是否仍在等待中: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 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

这是一个较旧的问题,但我正在尝试做类似的事情。我需要让n名工人继续前进。它们的结构是有前途的。我需要扫描并查看它们是否已解决,被拒绝或仍在等待处理。如果已解决,则需要该值;如果被拒绝,则需要执行一些操作来纠正问题或未决。如果解决或被拒绝,我需要启动另一个任务以使n继续进行。我找不到用Promise.all或Promise.race做到这一点的方法,因为我将诺言保持在数组中,找不到删除它们的方法。所以我创建了一个能解决问题的工人

我需要一个Promise Generator函数,该函数返回一个Promise,该Promise可以根据需要解决或拒绝。它由一个函数来调用,该函数设置框架以了解诺言正在做什么。

在下面的代码中,生成器仅基于setTimeout返回一个promise。

这里是

//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返回一个对象,其中包含promise及其状态和返回值。

以下代码运行一个循环,该循环测试状态并创建新的工作程序以将其保持在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

我发现此解决方案很简单,可以继续使用本机Promise,但可以添加有用的同步检查。我也不必引入整个Promise库。

CAVEAT:仅当当前执行线程中存在某种中断以允许诺言在检查同步结构之前执行时,此方法才有效。这使它的实用性比我最初想象的要有限-尽管对我的用例仍然有用(感谢Benjamin Gruenbaum指出了这一点)

/**
 * This function allow you to modify a JS Promise by adding some status properties.
 * Based on: http://stackoverflow.com/questions/21485545/is-there-a-way-to-tell-if-an-es6-promise-is-fulfilled-rejected-resolved
 * But modified according to the specs of promises : https://promisesaplus.com/
 */
function MakeQuerablePromise(promise) {
    // Don't modify any promise that has been already modified.
    if (promise.isFulfilled) return promise;

    // Set initial state
    var isPending = true;
    var isRejected = false;
    var isFulfilled = false;

    // Observe the promise, saving the fulfillment in a closure scope.
    var result = promise.then(
        function(v) {
            isFulfilled = true;
            isPending = false;
            return v; 
        }, 
        function(e) {
            isRejected = true;
            isPending = false;
            throw e; 
        }
    );

    result.isFulfilled = function() { return isFulfilled; };
    result.isPending = function() { return isPending; };
    result.isRejected = function() { return isRejected; };
    return result;
}

wrappedPromise = MakeQueryablePromise(Promise.resolve(3)); 
setTimeout(function() {console.log(wrappedPromise.isFulfilled())}, 1);

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);只要您执行此操作,就可以很好地工作。但是您必须了解这一事实,这样才能有用。我将在此警告中更新说明。我也同意函数命名可以更好/更惯用。
Akrikos

但是在那一点上,您可以then按照最初的承诺来完成同样的事情,因为它还是异步的。有一种方法process.binding('util').getPromiseDetails似乎确实起作用,但它使用的是私有API
Benjamin Gruenbaum,

一直以来都令人讨厌,这使得代码更难以理解。特别是当我只关心承诺是否被拒绝时-我的选择是将该状态存储在其他位置或执行类似操作。我承认在发布自己的歉意之前,我没有在这里仔细阅读其他解决方案。这个问题比我最初想到的要棘手。
Akrikos
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.