純粋なJavaScript Promise(組み込み実装またはポリフィル)があります:
var promise = new Promise(function (resolve, reject) { /* ... */ });
仕様 から、Promiseは次のいずれかになります。
Promiseを同期的に調査し、以下を決定するユースケースがあります。
プロミスは決着しましたか?
もしそうなら、約束は解決されますか?
#then()
を使用して、Promiseの状態が変更された後、非同期に実行される作業をスケジュールできることを知っています。私はこれを行う方法を尋ねていません。
この質問は、具体的にはPromiseの状態の同期尋問に関するものです。どうすればこれを達成できますか?
ネイティブJavaScriptプロミスにはこのような同期検査APIはありません。ネイティブプロミスでこれを行うことは不可能です。仕様では、このような方法は指定されていません。
ユーザーランドのライブラリはこれを行うことができ、特定のエンジン(v8など)をターゲットとし、プラットフォームコードにアクセスできる場合(つまり、 core)のコードを使用すると、特定のツール(プライベートシンボルなど)を使用してこれを実現できます。しかし、それはユーザーランドではなく、非常に具体的です。
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;
}
いいえ、同期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.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)の結果(遅延中のコメント)
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
ネイティブメソッドが提供されるまで、Node.jsで(ugい)ハックを使用できます。
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
}
実際、この基本的な機能が欠けているのは非常に迷惑です。 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つのプロミス状態を区別する方法はないようです。
これは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 };
};
これにより、デバッグ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 };
};
このようにして約束を包むことができます
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;
}
Bluebird.jsはこれを提供します: http://bluebirdjs.com/docs/api/isfulfilled.html
var Promise = require("bluebird");
let p = Promise.resolve();
console.log(p.isFulfilled());
独自のラッパーを作成したい場合は、ここに Nice blog があります。
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"]
私が最初にこの答えに到達したとき、それが私が探していたユースケースです。
ノードで、たとえば ドキュメント化されていない内部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> ]
警告:このメソッドは文書化されていない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
できることは、変数を使用して状態を保存し、その変数に状態を手動で設定し、その変数をチェックすることです。
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
私の解決策はより多くのコーディングですが、使用するすべての約束に対しておそらくこれを行う必要はないと思います。
Node.jsバージョン8では、 wise-inspection パッケージを使用して、ネイティブプロミスを(危険なハッキングなしで)同期的に検査できるようになりました。
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
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>
これは古い質問ですが、私は似たようなことをしようとしていました。私はn人の労働者を続けなければなりません。それらは約束で構成されています。スキャンして、それらが解決されているか、拒否されているか、まだ保留中かを確認する必要があります。解決された場合、値が必要です。拒否された場合は、問題を修正するために何かを行うか、保留中です。解決または拒否された場合、nを続行するには別のタスクを開始する必要があります。 Promise.allまたはPromise.raceでそれを行う方法がわかりません。アレイで約束を処理し続け、それらを削除する方法が見つかりません。だから私はトリックを行うワーカーを作成します
必要に応じて解決または拒否するpromiseを返すpromise generator関数が必要です。約束が何をしているのかを知るためにフレームワークを設定する関数によって呼び出されます。
以下のコードでは、ジェネレーターは単に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は、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でテスト済み
ところで、この答えにはあまりありませんが、同様のトピックに関する他の人では、誰かが「あなたが理解していない」または「それがどのように機能しないか」と言うと嫌いです。より良い方法を提案するのは素晴らしいことです。約束がどのように機能するかについての忍耐強い説明も良いでしょう。
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;
}
await
を @ jib's answer に使用し、慣用的なプロトタイピングを使用します。
Object.defineProperty(Promise.prototype, "state", {
get: function(){
const o = {};
return Promise.race([this, o]).then(
v => v === o ? "pending" : "resolved",
() => "rejected");
}
});
// usage:
(async () => {
console.log(await <Your Promise>.state);
console.log(await Promise.resolve(2).state); // "resolved"
console.log(await Promise.reject(0).state); // "rejected"
console.log(await new Promise(()=>{}).state); // "pending"
});
この非同期関数は、同期された関数のようにすぐに(または実際には瞬時に)「ほぼ」実行されることに注意してください。
resolved
フラグを持つpromiseラッパーを提供する小さなnpmパッケージ、promise-valueを作成しました。
https://www.npmjs.com/package/promise-value
また、promise値(またはエラー)への同期アクセスを提供します。これは、Promiseオブジェクト自体を変更せず、パターンを拡張するのではなく、ラップをたどります。