Promise
オブジェクトは非同期処理の最終的な完了処理 (もしくは失敗) およびその結果の値を表現します。
プロミスの挙動と使用法について学ぶには、最初に Promise の使用をお読みください。
解説
Promise
インターフェイスは作成時点では分からなくてもよい値へのプロキシです。 Promise を用いることで、非同期アクションの成功や失敗に対するハンドラーを関連付けることができます。これにより、非同期メソッドは、最終的な値を返すのではなく、未来のある時点で値を持つ Promise を返すことで、同期メソッドと同じように値を返すことができるようになります。
Promise
の状態は以下のいずれかとなります。
- 待機: 初期状態。成功も失敗もしていません。
- 満足: 処理が成功して完了したことを意味します。
- 拒絶: 処理が失敗したことを意味します。
待機状態のプロミスは、何らかの値を持つ満足 (fulfilled) 状態、もしくは何らかの理由 (エラー) を持つ拒絶 (rejected) 状態のいずれかに変わります。そのどちらとなっても、then
メソッドによって関連付けられたハンドラーが呼び出されます。 (対応するハンドラーが割り当てられたとき、既にプロミスが成功または失敗していても、そのハンドラーは呼ばれます。よって、非同期処理とその関連付けられたハンドラーとの競合は発生しません。)
メソッドと Promise.prototype.then()
メソッドもまた Promise を返すので、これらをチェーン (連鎖) させることができます。Promise.prototype.catch()
混乱を避けるために: Scheme に代表されるいくつかの言語では、遅延評価や計算を延期する機構を持っており、これらも "Promise" と呼ばれます。 JavaScript における Promise は、すでに起きつつある処理を表します。そしてこの処理はコールバックを使うことでチェーンさせることができます。式を遅延評価する方法を探しているのであれば、引数なしのアロー関数を考えてください。 f = () => expression
のように実現でき、遅延評価される式が作成され、 f()
を呼ぶことでその式を評価できます。
注: Promise は fulfilled か failed のどちらかになった場合は、 pending ではなく settled と呼ばれます。また解決 (resolved) という用語も目にされたことがあると思います。解決とは、Promise が解決または他の promise の状態にマッチするために" locked in "したことを意味します。States and fates では、 Promise の技術についてより詳細に述べられています。
連鎖したプロミス
promise.then()
, promise.catch()
, promise.finally()
の各メソッドは、決定したプロミスにさらなるアクションを関連付けるために使用されます。これらのメソッドはまた、新しく生成されたプロミスオブジェクトを返します。例えば、このようになります。
-
const myPromise = (new Promise(myExecutorFunc)) .then(handleFulfilledA,handleRejectedA) .then(handleFulfilledB,handleRejectedB) .then(handleFulfilledC,handleRejectedC); // または、おそらく次の方がよい ... const myPromise = (new Promise(myExecutorFunc)) .then(handleFulfilledA) .then(handleFulfilledB) .then(handleFulfilledC) .catch(handleRejectedAny);
拒絶されたプロミスの処理が早すぎると、プロミスの連鎖のさらに下の方に影響を及ぼします。エラーはすぐに処理しなければならないので、選択の余地がないこともあります。 (結果を処理するためのテクニックについては、下記の例の throw -999
を参照してください。) 一方で、すぐに必要がない場合は、最後の .catch() 文までエラー処理をしない方がシンプルです。
これら2つの関数のシグネチャはシンプルで、任意の型の引数を1つだけ受け取ることができます。これらの関数を書くのはプログラマーです。これらの関数の終了条件は、チェーン内の次のプロミスの「解決」状態を決定します。 throw
以外の終了条件は "解決" の状態を生み出し、一方、 throw
で終了すると "拒否" の状態を生み出します。
handleFulfilled(value) { /*...*/; return nextValue; }
handleRejection(reason) { /*...*/; throw nextReason; }
handleRejection(reason) { /*...*/; return nextValue; }
返される nextValue
は、別のプロミスオブジェクトにすることもでき、この場合はプロミスは動的にチェーンに挿入されます。
.then()
が適切な関数欠いている場合、処理は単純にチェーンの次のリンクへと続きます。したがってチェーンは、すべての handleRejection
を最後の .catch()
まで、安全に省略することができます。 同様に、.catch()
は、実際には handleFulfilled
用のスロットのないただの .then()
です。
プロミスのチェーンはロシアの人形のように入れ子にすることができますが、スタックの最上位のように取り出します。チェーンの最初のプロミスは最も深いところに入れ子になり、最初に取り出されます。
(promise D, (promise C, (promise B, (promise A) ) ) )
nextValue
がプロミスである場合、その効果は動的な置換です。 return
によってプロミスが取り出されますが、 nextValue
のプロミスはその場所に押し込まれます。上に示した入れ子では、"promise B" に関連付けられた .then()
が "promise X" の nextValue
を返すとします。 結果としての入れ子は以下のようになります。
(promise D, (promise C, (promise X) ) )
プロミスは複数の入れ子に参加することができます。以下のコードでは、 promiseA
が「確定」状態に移行すると、 .then()
の両方のインスタンスが呼び出されます。
const promiseA = new Promise(myExecutorFunc);
const promiseB = promiseA.then(handleFulfilled1, handleRejected1);
const promiseC = promiseA.then(handleFulfilled2, handleRejected2);
既に「解決済み」のプロミスにアクションを割り当てることができます。その場合、アクションは (適切であれば) 最初の非同期の機会に実行されます。プロミスは非同期であることが保証されていることに注意してください。したがって、既に「解決済み」のプロミスに対するアクションは、スタックがクリアされ、クロックティックが経過した後にのみ実行されます。この効果は setTimeout(action,10)
とよく似ています
const promiseA = new Promise( (resolutionFunc,rejectionFunc) => {
resolutionFunc(777);
});
// この時点で、 "promiseA" はすでに解決されています。
promiseA.then( (val) => console.log("asynchronous logging has val:",val) );
console.log("immediate logging");
// 以下の順序で出力が行われます。
// immediate logging
// asynchronous logging has val: 777
コンストラクター
Promise()
- 新しい
Promise
オブジェクトを生成します。このコンストラクターは主にまだプロミスに対応していない関数をラップするために使われます。
静的メソッド
Promise.all(iterable)
- すべてのプロミスが解決されるか、拒否されるかするまで待ちます。
- 返却されたプロミスが解決された場合、解決されたプロミスが、複数のプロミスが含まれる iterable で定義された通りの順番で入った集合配列の値によって解決されます。
- 拒否された場合は、 iterable の中で拒否された最初のプロミスの理由によって拒否されます。
Promise.allSettled(iterable)
- すべての Promise が完了する (それぞれが解決するか、拒否される) まで待ちます。
- Promise を返し、これはすべての与えられた Promise が解決または拒否された後で、それぞれの Promise の結果を記述するオブジェクトの配列で解決されます。
Promise.any(iterable)
- Promise オブジェクトの反復可能オブジェクトを取り、反復可能オブジェクトの中のプロミスのうちの一つが満足され次第、そのプロミスから受け取った値で解決する単一のプロミスを返します。
Promise.race(iterable)
- Promise のうちの1つが解決または拒否されるまで待ちます。
- 返された Promise が解決された場合、 iterable の中で最初に解決された Promise の値によって解決されます。
- 拒否された場合、最初に拒否された Promise の理由によって拒否されます。
Promise.reject(reason)
- 与えられた理由で拒否された新しい
Promise
オブジェクトを返します。 Promise.resolve(value)
- 与えられた値で解決された新しい
Promise
オブジェクトを返します。もし値が thenable (つまりthen
メソッドを持っているオブジェクト) ならば、返される Promise はその thenable をたどり、その結果を採用します。そうでなければ、返される Promise は与えられた値で解決されます。 - 一般に、ある値がプロミスかどうかがわからない場合は、
Promise.resolve(value)
を使って Promise にして扱います。
インスタンスメソッド
Promise.prototype.catch()
- プロミスに失敗ハンドラーコールバックを付加します。呼ばれるとコールバックの返値、または、オリジナルのプロミスが成功しているなら、その成功値によって完了している新しいプロミスを返します。
Promise.prototype.then()
- プロミスに成功ハンドラーと失敗ハンドラーを付加します。呼ばれたハンドラーの戻り値によって解決している新しいプロミスを返します。または、プロミスが扱われなかった場合 (つまり
onFulfilled
やonRejected
が関数でない場合) には、元の完了した値に解決しているプロミスを返します。 Promise.prototype.finally()
- プロミスにハンドラーを付加し、元のプロミスが解決されたときに解決される新しいプロミスを返します。このハンドラーは、成功か失敗かに関わらず、元のプロミスが完了したときに呼ばれます。
例
基本的な使用例
let myFirstPromise = new Promise((resolve, reject) => {
// We call resolve(...) when what we were doing asynchronously was successful, and reject(...) when it failed.
// In this example, we use setTimeout(...) to simulate async code.
// In reality, you will probably be using something like XHR or an HTML5 API.
setTimeout( function() {
resolve("Success!") // Yay! Everything went well!
}, 250)
})
myFirstPromise.then((successMessage) => {
// successMessage is whatever we passed in the resolve(...) function above.
// It doesn't have to be a string, but if it is only a succeed message, it probably will be.
console.log("Yay! " + successMessage)
});
多様な状況に対応した例
この例では、プロミス機能を使用するための多様なテクニックと、発生する可能性のある多様な状況を示しています。これを理解するには、まずコードブロックの一番下までスクロールして、プロミスの連鎖を調べてください。最初のプロミスが提供されると、プロミスの連鎖が続きます。このチェーンは .then()
の呼び出しで構成され、通常は (必ずしもそうとは限りませんが) 最後に単一の .catch()
があり、オプションで .finally()
が続きます。この例では、プロミスチェーンはカスタムで書かれた new Promise()
コンストラクターによって開始されますが、実際には、プロミスチェーンは通常、プロミスを返す API 関数 (他の誰かが書いたもの) から開始されます。
関数 tetheredGetNumber()
の例では、非同期呼び出しを設定している間、またはコールバック内で、またはその両方で reject()
を使用してプロミスを生成することを示しています。 関数 promiseGetWord()
は、API 関数がどのように自己完結型の方法でプロミスを生成して返すかを示しています。
関数 troubleWithGetNumber()
は throw()
で終わることに注意してください。これは、 ES6 のプロミスチェーンでは、エラーが発生した後で、 "throw()" がなく、エラーが "fixed" であるようにみえても、すべての .then()
のプロミスを通過するため、強制的に行われています。これは面倒なので、 .then()
プロミスのチェーン全体で rejectionFunc
を省略して、最終的な catch()
で単一の rejectionFunc
を使用するのが一般的です。 別の方法としては、特別な値を投げるという方法があります (この場合は"-999" ですが、カスタムのエラー種別の方が適切です)。
このコードは NodeJS で実行できます。実際にエラーが発生しているのを見ることで理解度が高まります。より多くのエラーを強制的に発生させるには、 threshold
の値を変更します。
"use strict";
// To experiment with error handling, "threshold" values cause errors randomly
const THRESHOLD_A = 8; // can use zero 0 to guarantee error
function tetheredGetNumber(resolve, reject) {
try {
setTimeout(
function() {
const randomInt = Date.now();
const value = randomInt % 10;
try {
if(value >= THRESHOLD_A) {
throw new Error(`Too large: ${value}`);
}
} catch(msg) {
reject(`Error in callback ${msg}`);
}
resolve(value);
return;
}, 500);
// To experiment with error at set-up, uncomment the following 'throw'.
// throw new Error("Bad setup");
} catch(err) {
reject(`Error during setup: ${err}`);
}
return;
}
function determineParity(value) {
const isOdd = value % 2 ? true : false ;
const parityInfo = { theNumber: value, isOdd: isOdd };
return parityInfo;
}
function troubleWithGetNumber(reason) {
console.error(`Trouble getting number: ${reason}`);
throw -999; // must "throw" something, to maintain error state down the chain
}
function promiseGetWord(parityInfo) {
// The "tetheredGetWord()" function gets "parityInfo" as closure variable.
var tetheredGetWord = function(resolve,reject) {
const theNumber = parityInfo.theNumber;
const threshold_B = THRESHOLD_A - 1;
if(theNumber >= threshold_B) {
reject(`Still too large: ${theNumber}`);
} else {
parityInfo.wordEvenOdd = parityInfo.isOdd ? 'odd' : 'even';
resolve(parityInfo);
}
return;
}
return new Promise(tetheredGetWord);
}
(new Promise(tetheredGetNumber))
.then(determineParity,troubleWithGetNumber)
.then(promiseGetWord)
.then((info) => {
console.log("Got: ",info.theNumber," , ", info.wordEvenOdd);
return info;
})
.catch((reason) => {
if(reason === -999) {
console.error("Had previously handled error");
}
else {
console.error(`Trouble with promiseGetWord(): ${reason}`);
}
})
.finally((info) => console.log("All done"));
応用例
<button id="btn">Make a promise!</button>
<div id="log"></div>
以下の例は Promise
の仕組みを示したものです。 testPromise()
メソッドは <button>
をクリックする度に呼び出されます。testPromise()
メソッドは、 window.setTimeout()
を用いて、1秒から 3秒のランダムな時間の後、メソッドがこれまでに呼ばれた回数で成功する Promise を作成します。 Promise()
コンストラクターは Promise を作成するために使用されます。
プロミスが満足したことは、 p1.then()
で設定されたコールバックによって記録されます。この記録から、メソッドの同期処理部分が、 Promise による非同期処理からどのように分離されているかがわかります。
'use strict';
var promiseCount = 0;
function testPromise() {
let thisPromiseCount = ++promiseCount;
let log = document.getElementById('log');
log.insertAdjacentHTML('beforeend', thisPromiseCount +
') 開始 (<small>同期処理開始</small>)<br/>');
// 新しい Promise を作成: 1~3秒後に結果を返すことを約束します
let p1 = new Promise(
// executor 関数は Promise の成功または失敗に応じて呼ばれます
//
(resolve, reject) => {
log.insertAdjacentHTML('beforeend', thisPromiseCount +
') Promise 開始 (<small>非同期処理開始</small>)<br/>');
// 非同期を作成するための一例です
window.setTimeout(
function() {
// 約束を果たしました!
resolve(thisPromiseCount);
}, Math.random() * 2000 + 1000);
}
);
// Promise が成功した時に何をするかを定めます then() で成功した時
// catch() で失敗した時
p1.then(
// メッセージと値を記録します
function(val) {
log.insertAdjacentHTML('beforeend', val +
') Promise 成功 (<small>非同期処理終了</small>)<br/>');
}).catch(
// 失敗した理由を記録します
(reason) => {
console.log('Handle rejected promise ('+reason+') here.');
});
log.insertAdjacentHTML('beforeend', thisPromiseCount +
') Promise は作成されました (<small>同期処理終了</small>)<br/>');
}
if ("Promise" in window) {
let btn = document.getElementById("btn");
btn.addEventListener("click",testPromise);
} else {
log = document.getElementById('log');
log.innerHTML = "Live example not available as your browser doesn't support the <code>Promise<code> interface.";
}
この例はボタンをクリックすると実行されます。 (ブラウザーが Promise
に対応している必要があります。)
短い時間の間に何度かボタンをクリックすると、それぞれの promise が次々と成功するのがわかります。
XHR による画像の読み込み
Promise
と XMLHttpRequest
で画像を読み込む別の例は、 MDN GitHub js-examples リポジトリにあり、動作を確認することができます。それぞれの行のコメントで Promise と XHR の構造がよくわかるはずです。
仕様書
ブラウザーの互換性
BCD tables only load in the browser
関連情報
- プロミスの使用
- Promises/A+ specification
- Venkatraman.R - JS Promise (Part 1, Basics)
- Venkatraman.R - JS Promise (Part 2 - Using Q.js, When.js and RSVP.js)
- Venkatraman.R - Tools for Promises Unit Testing
- Jake Archibald: JavaScript Promises: There and Back Again
- Domenic Denicola: Callbacks, Promises, and Coroutines – Asynchronous Programming Patterns in JavaScript
- Matt Greer: JavaScript Promises ... In Wicked Detail
- Forbes Lindesay: promisejs.org
- Speed-polyfill to polyfill both promise availability and promise performance.
- Promise polyfill
- Udacity: JavaScript Promises