JavaScript Promiseの状態を同期的に判断するにはどうすればよいですか?


149

純粋なJavaScriptプロミス(組み込み実装またはポリフィル)があります。

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

より仕様、約束は、のいずれかになります。

  • 「解決済み」と「解決済み」
  • 「解決済み」と「拒否」
  • '保留中'

Promiseに同期的に問い合わせて、次のことを決定する使用例があります。

  • Promiseは解決しましたか?

  • もしそうなら、約束は解決されますか?

#then()Promiseの状態が変化した後に非同期で実行されるように作業をスケジュールするのに使用できることを知っています。私はこれを行う方法を尋ねていません。

この質問は、特にPromiseの状態の同期問い合わせに関するものです。どうすればこれを達成できますか?


6
外部から見えるプロミスにプロパティを設定し、then()を使用してプロパティを変更します。
dandavis 2015年

@jokeyrhyme fwiw、v8ソースコード.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]]: "resolved"、[[PromiseValue]]: "baz"} proto:Promise [[PromiseStatus]]: "resolved" [[PromiseValue]]: "baz 「そして、人々はそれを行うことができないと主張します。Chromeはそれをどのように行っていますか?(Angular plnkr.co/edit/IPIWgLJKQStI5ubXmcsFを使用
JGFMK

ノードv11.12.0のconsole.logを使用すると、promise状態が表示されます。EG console.log(Promise.new((resolve, reject) => {})=>Promise { <pending> }
プルゼ

回答:


77

このような同期検査APIは、ネイティブJavaScriptのpromiseには存在しません。これをネイティブの約束で行うことは不可能です。この仕様では、このような方法を指定していません。

ユーザーランドライブラリがこれを行うことができます。特定のエンジン(v8など)を対象としていて、プラットフォームコードにアクセスできる場合(つまり、コアにコードを記述できる場合)、特定のツール(プライベートシンボルなど)を使用してこれを実現できます。 。それはユーザーランドではなく、非常に特殊です。


4
注:私は正直に言って、同期検査の使用例はほとんどなく、非常にまれです。同期検査なしでそれを達成する方法を尋ねる新しい質問で具体的な使用例を共有する場合誰かが答えない場合は一発で答えます私をそれに打ちのめします:)
Benjamin Gruenbaum

4
ユースケースがまれであっても、このようなものを含めるとどのような害がありますか?前のジョブが完了したかどうか、および別のジョブを要求できるかどうかを確認するには、このようなステータスチェックが必要です。また、オブジェクトは予告なしに所有者を変更する可能性があるため、外部変数を設定することはできません。私がそれを検査するときに私にそれを示すので、Node.jsがこの情報にアクセスできるのを見ることができますが、文字列を解析する以外にそれを取得する方法がありません。
Tustin2121 2016年

9
したがって、ネイティブの約束は非現実的であり、常にブルーバードを使用するため、破棄する必要があります。素晴らしいニュース!ネイティブプロミスを非推奨にしてノードエンジンから破棄するように提案するにはどうすればよいですか?
user619271 16

1
.anyマークが主張したので、多くのこと、私たちは代わりに仕様を決めてミスをするべきでした。1つPromise.race([])は、永久に保留中のプロミスです(エラーではありません)。通常、最初のプロミスだけでなく、最初に成功したプロミスが必要です。とにかく、それは尋ねられた質問にはあまり関係ありません-OPは同期検査について尋ねましたが.race、その多くの欠点についてではありませんでした。
Benjamin Gruenbaum 2017年

5
@Akrikosの回答では、Promiseの状態を同期的に検査することはできません。たとえばMakeQueryablePromise(Promise.resolve(3)).isResolved、falseですが、Promiseは明らかに解決されます。言うまでもなく、回答でも「解決済み」と「達成済み」という用語が誤って使用されています。それを行うには、.thenハンドラーを自分で追加するだけでよく、同期検査のポイントを完全に逃してしまいます。
Benjamin Gruenbaum

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

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、ありがとう!答えを簡略化しました。
ジブ2017

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 in delayにコメント)

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

promiseState make .raceおよび.then:レベル2


3
の代わりに'a value that p should not return'記号を
programmer5000

1
@ programmer5000利点は何ですか?
Moritz Schmitz対Hülst2018年

2
@MoritzSchmitzv.HülstaはSymbol一意の値になるため、「値[...] pが返すべきではないもの」を推測する必要はありません。ただし、特定のオブジェクトへの参照も同様に機能します。
Scott Rudiger、2018年

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
それは恐ろしいことです。
John Weisz

@JohnWeisz最悪なのは、後方互換性の欠如です。私はすべてが同期的であると想定するコードベースに約束のAPIを統合しようとしています。それは恐ろしいことをしている、またはコードの巨大なチャンクを書き直している。いずれにせよ、私は残虐行為を犯しています。
rath

4
使用するだけprocess.binding('util').getPromiseDetails
amara 2017年

@ 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はシングルスレッドであるため、これを仕様に含めることを正当化するのに十分な一般的なユースケースを見つけるのは困難です。promiseが解決されたかどうかを知るのに最適な場所は.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は同期検査について具体的に尋ね、非同期検査についてはすでに知っていると述べました。
Benjamin Gruenbaum 2015年

@BenjaminGruenbaum:同じ「ターン」のコードがデフォルト値を呼び出した場合、デフォルト値は表示されませんか?
dandavis 2015年

もちろん、作成時にすべての約束をラップする必要があります。たとえば、それらを作成して返す関数の内部。
SpiderPig 2015年

3
そうです、その時点でそれらは実際にはネイティブのプロミスではありません。オブジェクトのサルのパッチプロパティの代わりにエレガントにこれを実行できるようにするサブクラスで拡張することを意図した方法でそれらを拡張することもできます。
Benjamin Gruenbaum 2015年

私が示した方法でプロミスを拡張する場合でも、サブクラス化する場合でも、いずれの場合も独自のバージョンを追加してキャッチする必要があります。
SpiderPig 2015年

5

この基本的な機能が欠けていることは確かに非常に迷惑です。node.jsを使用している場合、2つの回避策を知っていますが、どちらも非常にきれいではありません。以下の両方のスニペットは同じ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' }

いずれかのトリックを使用して最後の2つのプロミス状態を区別する方法はないようです。

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が完全に適切です。
Benjamin Gruenbaum

そして.. DeprecationWarning: DebugContext has been deprecated and will be removed in a future version.:( V8がそれを削除したように見えます
ベンジャミン・グルエンバウム

我々 (ノード)は、完全にAPIのためのV8を頼むか、けれども直接約束の状態を見てのためのAPIを公開することができます-あなたがで問題を開くとgithub.com/nodejs/promise-use-cases私は喜んV8でそれが表示されます
ベンジャミングレンバウム

1
このトピックのさらに下のコメントでは、APIがすでに存在しているように見えます:保留中、実行済み、拒否のprocess.binding('util').getPromiseDetails( promise )戻り。[ 0, ][ 1, value ][ 2, value ]
Matthijs 2018

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です。何が問題なのかを知るにはどうすればよいですか?
Adam Barnes、

うーん、私はそれが内で動作していることを覚えていますmocha。でもそれを試したことはjestありません。ここで新しい質問リンクを開始して、Node.jsバージョンとバージョンを含めてみjestませんか?
Scott Rudiger、

残念ながら、これ以上私が興味を持っていることはありません。基本的にPromise、私がPromise保留中の間に行われるべきことをテストするためにのみ使用していた手動で解決/拒否できるサニティテストを探していましたが、私が書いたものが機能する限り、それをテストする必要はありませんでしたそれに依存するものに加えて。
Adam Barnes、

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を使用するソリューションについても同じです)。私の2番目のソリューションはこの問題を解決します。

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

2番目の(そして正しい)ソリューション:

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、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の約束クラスは、新規作成する照会可能約束クラスを。

たとえば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)。2番目の引数として忘れないでください。そうしないと、thenエラーが正しく伝達されません(Promiseコンストラクターのアンチパターンと見なされる理由の中で)。ただし、これは本当に同期的ではありません(たとえば、既に解決されたプロミスを検出しない)が、呼び出し元を制御せず、答えがすぐに必要な場合に役立つでしょう。
ジブ

1

オブジェクト全体を文字列に変換するだけでプロミスがまだ保留中であるかどうかを確認するエレガントでハック別の方法があり、次のようにinspectを使用して確認します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を配列で処理し続け、削除する方法が見つかりません。だから私はトリックを行うワーカーを作成します

必要に応じて解決または拒否するプロミスを返すプロミスジェネレーター関数が必要です。これは、フレームワークを設定して、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ライブラリ全体を取り込む必要もありませんでした。

警告:これは、Promiseが同期構造をチェックする前に実行できるように、現在の実行スレッドに何らかのブレークがある場合にのみ機能します。これは、私が最初に思ったよりも有用性が限定されたものになります-私のユースケースでもまだ有用です(これを指摘してくれた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の約束が履行/拒否/解決されたかどうかを教えてください。


私の答えに関するコメントに追加されているように-これは完全に正しくありません。つまり、Promiseの状態を同期的に検査することはできません-たとえばMakeQueryablePromise(Promise.resolve(3)).isResolvedfalseですが、Promiseは明らかに解決されます。言うまでもなく、回答でも「解決済み」と「実行済み」という用語が誤って使用されています。それを行うには、.thenハンドラーを自分で追加するだけでよく、同期検査のポイントを完全に逃してしまいます。
Benjamin Gruenbaum

私はあなたが言っていることを理解し、あなたは良い点を作ります。JSのシングルスレッドの性質が邪魔になっているのではないでしょうか。promiseを解決済みとしてマークするには、現在の実行を中断する必要があります。let wrappedPromise = MakeQueryablePromise(Promise.resolve(3)); setTimeout(function() {console.log(wrappedPromise.isFulfilled())}, 1);これを行う限り、これはうまく機能します。しかし、これが役立つためには、その事実を理解する必要があります。その警告で説明を更新します。また、関数の命名がより良い/より慣用的である可能性があることに同意します。
Akrikos 2018

しかし、その時点ではthen非同期であるので、元の約束だけで同じことを達成できます。process.binding('util').getPromiseDetails動作するように見える方法はありますが、プライベートAPIを使用しています
Benjamin Gruenbaum

それを常にしなければならないことは不愉快であり、コードを理解することをはるかに困難にします。特に私が気にするのは、約束が拒否されたかどうかだけです。つまり、私の選択肢は、その状態を別の場所に保存するか、このようなことをすることです。自分で投稿する前に、ここで他の解決策を十分に読んでいないことを認めます。この問題は、私が最初に思ったよりも厄介です。
Akrikos
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.