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

jok*_*yme 132 javascript promise es6-promise

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

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

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

  • '已定居'和'已解决'
  • '已定居'和'被拒绝'
  • "待定"

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

  • 承诺落户了吗?

  • 如果是这样,Promise是否已解决?

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

这个问题具体是关于Promise状态的同步审讯.我怎样才能做到这一点?

Ben*_*aum 66

对于本机JavaScript承诺,不存在此类同步检查API.用本机承诺做这件事是不可能的.规范没有指定这样的方法.

Userland库可以做到这一点,如果您的目标是特定引擎(如v8)并且可以访问平台代码(也就是说,您可以在核心中编写代码),那么您可以使用特定工具(如私有符号)来实现此目的.这是超级特定的,而不是用户区.

  • 所以我们必须抛弃原生的承诺,因为它们是不切实际的,总是使用蓝鸟.好消息!我如何提出本地承诺被弃用并抛出节点引擎? (8认同)
  • @Akrikos不能让您同步检查承诺的状态-例如,`MakeQueryablePromise(Promise.resolve(3))。isResolved`是错误的,但是承诺显然得到了解决。更不用说答案也错误地使用了术语“已解决”和“已实现”。为此,您可以自己添加一个.then处理程序,这完全错过了同步检查的要点。 (5认同)
  • 注意:我真的相信同步检查的用例很少而且非常罕见,如果你分享你的具体用例_在一个新的问题_询问如何在没有同步检查的情况下实现它 - 如果有人不这样做,我会回答它打败了我:) (4认同)
  • 即使用例很少,包括这样的东西有什么危害呢?我需要这样的状态检查,看看以前的工作是否完成,以及我是否可以申请另一份工作.我不能只设置一个外部变量,因为该对象有可能在没有通知的情况下更改所有者.更令人恼火的是我可以看到Node.js可以访问这些信息,因为当我检查它时它会显示给我,但除了解析字符串之外没有办法解决它? (2认同)
  • @ user619271 有很多方法可以调试承诺。仅仅因为它们不能以您想要调试的方式进行调试并不会使它们变得不切实际。我多年来一直在使用 Promise 并且从未需要同步获取 Promise 的状态。所以“我们必须抛弃原生承诺”的说法是废话。 (2认同)

0xa*_*xaB 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;
}
Run Code Online (Sandbox Code Playgroud)

  • @Klesun,考虑到已经有一个已经被接受的答案,也许这对更多人来说是一个足够好的解决方案,而不仅仅是OP? (8认同)
  • 重要的是只使用纯函数,没有任何工件和侧面突变我的承诺,这非常好<3 (4认同)
  • OP询问了如何同步进行操作 (2认同)
  • 解决方案非常简单,没有任何额外的样板代码围绕我的承诺。所以这不是OP的完全答案,但仍然是最好的解决方法。 (2认同)

jib*_*jib 19

不,没有同步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
Run Code Online (Sandbox Code Playgroud)

  • +1 来这里正是为了寻找这个。这是一个完美的例子,这个答案没有回答原来的问题,但对很多登陆这里的人来说仍然非常有用。 (8认同)
  • 谢谢@Matthijs!我已经简化了我的答案。 (3认同)
  • 这种结构背后是否有特定的原因?这对我来说似乎不必要地复杂。据我所知,它的工作原理相同:`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 是待定的。 (2认同)

小智 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);
  });
}
Run Code Online (Sandbox Code Playgroud)

一个小脚本,用于测试和理解它们异步的含义

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');
Run Code Online (Sandbox Code Playgroud)

延迟结果(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
Run Code Online (Sandbox Code Playgroud)

和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
Run Code Online (Sandbox Code Playgroud)

promiseState make .race和.then:Level 2

  • 使用[符号](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol)而不是"不应该返回的值". (3认同)
  • @MoritzSchmitzv.Hülst一个“ Symbol”将是一个唯一值,因此您不必猜测“值p不应返回”。但是,对特定对象的引用也将起作用。 (2认同)

rab*_*tco 8

您可以在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
}
Run Code Online (Sandbox Code Playgroud)

  • 那是_horrendous_. (5认同)
  • 只需使用`process.binding('util').getPromiseDetails` (4认同)
  • 我把它煮成了一个polyfill:`Promise.prototype.isPending = function(){return util.inspect(this).indexOf("<pending>")> - 1; }` (3认同)

Val*_*len 8

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"
})();
Run Code Online (Sandbox Code Playgroud)

请注意,这个异步函数“几乎”像同步函数一样立即执行(或者实际上可能立即执行)。


Joh*_*nRC 6

我浏览了针对此问题提出的解决方案,但没有找到与我在 Node.js 中使用的简单方法相对应的解决方案。

我定义了一个简单的 PromiseMonitor 类,它将 Promise 作为其构造函数的单个参数,并具有一个字符串属性,.status该属性返回与 Promise 状态“pending”、“resolved”或“rejected”相对应的标准字符串值,以及四个布尔属性.pending.resolved.rejected.error。仅当为 true并且拒绝回调传递了 Error 对象时,该属性.error才设置为 true 。.rejected

该类只是.then()在 Promise 被解决或拒绝时使用 Promise 来更改 PromiseMonitor 的状态。它不会干扰原始承诺的任何其他用途。这是代码:

class PromiseMonitor {
    constructor(prm){
        this._status = "pending";
        this._pending = true;
        this._resolved = false;
        this._rejected = false;
        this._error = false;
        prm
            .then( ()=>{  
                        this._status = "resolved"; 
                        this._resolved = true; 
                        this._pending = false; 
                    } 
                , (err)=>{ 
                        this._status = "rejected";
                        this._pending = false;
                        this._rejected = true;
                        this._error = err instanceof Error ? true: false ; 
                    } 
                );
    }

    get status(){ return this._status; };
    get pending(){ return this._pending; };
    get resolved(){ return this._resolved; };
    get rejected(){ return this._rejected; };
    get error(){ return this._error };
};
Run Code Online (Sandbox Code Playgroud)

要监视 Promise 的状态,只需创建 PromiseMonitor 的实例,并将 Promise 作为参数传入,例如:

let promiseObject = functionThatReturnsAPromise();
let promiseMonitor = new PromiseMonitor( promiseObject );
Run Code Online (Sandbox Code Playgroud)

现在您可以同步检查 PromiseMonitor 的所有属性,它将跟踪原始 Promise 的状态。这是一个测试脚本,演示了正在监视的 Promise 的三种可能的解决方案。

let ticks = 0;
let tickerID = setInterval( ()=>{++ticks; console.log(`..tick ${ticks}`)}, 1000);

async function run(){
    console.log("Start");

    let delay = prmDelay(2000);
    let delayMonitor = new PromiseMonitor(delay);

    // normal handling of delay promise
    delay.then((result)=>( console.log("Normal resolution of delay using .then()") ) );

    console.log("delay at start:\n", delay);
    console.log("delayMonitor at start:\n", delayMonitor);
    await delay;
    console.log("delay finished:\n", delay);
    console.log("delayMonitor finished:\n", delayMonitor);


    console.log("\n\n TEST2: Rejection without an Error test ================================")
    let rejDelay = prmDelay(3000, "reject");
    let rejMonitor = new PromiseMonitor(rejDelay);

    // normal handling of reject result on promise
    rejDelay.then((result)=>( console.log("Normal resolution of rejDelay using .then will not happen") ) 
                    , (err)=>( console.log("Rejection of rejDelay handled using .then")));

    console.log("rejDelay at start:\n", rejDelay);
    console.log("rejMonitor at start:\n", rejMonitor);

    await rejDelay.catch( (err)=>{ console.log( "Caught error using .catch on rejDelay" ); });

    console.log("rejDelay finished:\n", rejDelay);
    console.log("rejMonitor finished:\n", rejMonitor);


    console.log("\n\n TEST3: Rejection with an Error test ================================")
    let errMonitor ;
    let errDelay;
    try{

        errDelay = prmDelay(1000, "error");
        errMonitor = new PromiseMonitor(errDelay);
        
        // normal handling of results of the original promise
        errDelay.then(
            (result)=>{ 
                console.log("Normal expiry of errDelay");
                console.log("Monitor Status is " + errMonitor.status )
            } 
            , (err)=>{
                console.log("** Rejection of errDelay handled using .then()");
                console.log("   Monitor Status is " + errMonitor.status )
            }
        );

        console.log("errDelay at start:\n", errDelay);
        console.log("errMonitor at start:\n", errMonitor);

        await errDelay;

        console.log("**** This should never be run");

    } catch(err) { 

        console.log( "** Caught error on errDelay using try{}catch{}:" ); 
        console.log( "   Monitor Status is " + errMonitor.status )

    };

    console.log("errDelay finished:\n", errDelay);
    console.log("errMonitor finished:\n", errMonitor);
    

    clearInterval(tickerID);


}

/**
 * Creates a new promise with a specific result
 * @param {*} tt 
 * @param {*} exitType ("resolve", "reject" or "error")
 */
function prmDelay (tt, exitType) {
    
    return new Promise(function(resolve, reject) {
        if( exitType == 'reject' ){
            setTimeout(()=>{ reject("REJECTED")}, tt);
        } else if( exitType== 'error'){
            setTimeout(()=>{ reject(new Error( "ERROR Rejection") ); }, tt);
        } else {
            setTimeout(()=>{ resolve("RESOLVED") }, tt);
        } ;
    });
};


run();
Run Code Online (Sandbox Code Playgroud)


Spi*_*Pig 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;
}
Run Code Online (Sandbox Code Playgroud)

  • 这将需要 OP 在事件循环的前一回合中获得对承诺的访问权。由于 `.then` 总是异步执行,OP 想要检查 _在同一轮中_的承诺不会在这里得到正确的结果。注意 OP 专门询问了 _synchronous_ 检查,并提到他们已经了解异步检查。 (5认同)
  • 是的,在这一点上,它们不再是真正的原生承诺,您不妨按照它们打算通过子类进行扩展的方式来扩展它们,这将允许您优雅地执行此操作,而不是在对象上修补属性。 (3认同)

Mic*_*ole 5

更新时间:2019年

Bluebird.js提供了以下功能:http : //bluebirdjs.com/docs/api/isfulfilled.html

var Promise = require("bluebird");
let p = Promise.resolve();
console.log(p.isFulfilled());
Run Code Online (Sandbox Code Playgroud)

如果您想创建自己的包装器,那么这里是一个不错的博客

因为JavaScript是单线程的,所以很难找到足够通用的用例来证明将其放入规范中是合理的。知道承诺是否得到解决的最佳位置是.then()。测试Promise是否已满,将创建一个轮询循环,这很可能是错误的方向。

如果您想同步推理异步代码,那么async / await是一个不错的构造。

await this();
await that();
return 'success!';
Run Code Online (Sandbox Code Playgroud)

另一个有用的调用是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"]
Run Code Online (Sandbox Code Playgroud)

当我第一次获得这个答案时,这就是我要寻找的用例。


Mat*_*ijs 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' }
Run Code Online (Sandbox Code Playgroud)

似乎没有任何方法可以使用任何一种技巧来区分最后两个承诺状态。

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 };
};
Run Code Online (Sandbox Code Playgroud)

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 };
};
Run Code Online (Sandbox Code Playgroud)


ama*_*ara 5

在节点中,说未记录的内部 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> ]
Run Code Online (Sandbox Code Playgroud)

  • 请在您的答案中提供更多详细信息。即使您的答案是一个完整的解决方案,如果您花时间解释您的代码的功能以及如何提出该代码,询问者也会学到更多。 (2认同)
  • 虽然此代码段可能是解决方案,但[包括说明](// meta.stackexchange.com/questions/114762/explaining-entrely-‌基于代码的答案)确实有助于提高您的帖子质量。请记住,您将来会为读者回答这个问题,而这些人可能不知道您提出代码建议的原因。 (2认同)
  • `process.binding('util').getPromiseDetails` 在节点 16 上是 `undefined`! (2认同)

归档时间:

查看次数:

41572 次

最近记录:

5 年,11 月 前