Promise

Promise オブジェクトは非同期処理の最終的な完了処理 (もしくは失敗) およびその結果の値を表現します。

Promise の挙動と使用法について学ぶには、最初に Promise の使用をお読みください。

解説

Promise インターフェイスは作成時点では分からなくてもよい値へのプロキシです。Promise を用いることで、非同期アクションが最終的に成功した時の値や失敗した時の理由に対するハンドラーを関連付けることができます。これにより、非同期メソッドは、最終的な値をすぐに返す代わりに、未来のある時点で値を持つ Promise を返すことで、同期メソッドと同じように値を返すことができるようになります。

Promise の状態は以下のいずれかとなります。

  • pending: 初期状態。成功も失敗もしていません。
  • fulfilled: 処理が成功して完了したことを意味します。
  • rejected: 処理が失敗したことを意味します。

pending 状態の Promise は、何らかの値を持つ fulfilled 状態、もしくは何らかの理由 (エラー) を持つ rejected 状態のいずれかに変わります。そのどちらとなっても、then メソッドによって関連付けられたハンドラーが呼ばれます。(対応するハンドラーがアタッチされたとき、既に Promise が成功または失敗していても、そのハンドラーは呼ばれます。よって、非同期処理とその関連付けられたハンドラーとのレースコンディションは発生しません。)

Promise.prototype.then() メソッドと Promise.prototype.catch() メソッドもまた Promise を返すので、これらをチェーン (連鎖) させることができます。

混乱を避けるために: Scheme に代表されるいくつかの言語では、遅延評価や計算を延期する機構を持っており、これらも "Promise" と呼ばれます。JavaScript における Promise は、すでに起きつつある処理を表します。そしてこの処理はコールバックを使うことでチェーンさせることができます。式を遅延評価する方法を探しているのであれば、引数なしのアロー関数を考えてください。f = () => expression のように実現でき、遅延評価される式が作成され、f() を呼ぶことでその式を評価できます。

メモ: Promise は fulfilled failed のどちらかになった場合は、pending ではなく settled と呼ばれます。また解決 (resolved) という用語も目にされたことがあると思います。解決とは、Promise が解決または他の promise の状態にマッチするために"locked in"したことを意味します。States and fates では、Promise の技術についてより詳細に述べられています。

コンストラクタ

Promise()
コンストラクタは主に、まだ Promise をサポートしていない関数をラップするのに使用されます。

コンストラクタの文法

var promiseObj = new Promise(executor);

引数

executor
コンストラクタが promiseObj を作成する間にコンストラクタによって実行される 関数executor は promise に結果を結びつけるカスタムコードです。プログラマーが executor を書きます。関数の形はこのようになります:
function(resolutionFunc, rejectionFunc){
    // typically, some asynchronous operation.
} 
コンストラクタが新しい promiseObj を生成したとき、対応する resolutionFuncrejectionFunc の関数のペアも生成します。これは promiseObj に"束縛されて"います。このため、executor 内のコードは命令を実行する機会があり、次に(値が別の Promise オブジェクトでない場合)命令の結果を、"fulfilled" か "rejected" のいずれかによってそれぞれ resolutionFuncrejectionFunc を実行することで反映します。
executor は意味のある戻り値はありません。これは resolutionFuncrejectionFunc の副作用と通信します。この副作用は promiseObj が "resolved"になることです。
通常は、このように動作します:  executor 内の命令は非同期でコールバックを提供します。このコールバックは executor コード内で定義されます。このコールバックは resolutionFunc の実行で終了します。resolutionFunc の実行は value パラメーターを含みます。この value は束縛された promiseObj に戻されます。promiseObj は(非同期に) 関連した .then() を実行します。valuepromiseObj.then() に受け取られて、handleFulfilled の実行の入力パラメーターとして渡されます ("チェーン Promises" セクションを見てください)。
executor には、エラーで rejectionFunc を実行する try{} catch() ブロックを入れることもあります。
この 2 つの関数シグネチャは簡単で、あらゆるタイプの単一パラメーターを取ります。もちろん、この関数の実際の名前は何とでもできます。つまり executor のパラメーターで名前をつけられます。それぞれの関数は単に適当なときに呼ぶことで使用できます。
resolutionFunc(value) // call on fulfilled
rejectionFunc(reason) // call on rejected

戻ってきた value はもう1つの promise オブジェクトになり、その場合は promise が動的にチェーンに挿入されます。

戻り値

promiseObj
new から呼ばれたとき、Promise コンストラクタは promise オブジェクトを返します。この promise オブジェクトは resolutionFuncrejectionFunc 関数が実行されたとき "resolved" になります。resolutionFuncrejectionFunc を呼び出してその他の Promise オブジェクトを引数として渡した場合、"resolved"と呼ぶことはできますが、"settled"とは言えません。

チェーンされた Promises

promise.then()promise.catch()promise.finally() の各メソッドを使用して、その後のアクションを解決する promise に関連付けます。これらのメソッドは、チェーンにオプションで使用できる新しく生成された promise オブジェクトも返します。たとえば、次のように:

const myPromise = 
  (new Promise(myExecutorFunc))
  .then(handleFulfilledA,handleRejectedA)
  .then(handleFulfilledB,handleRejectedB)
  .then(handleFulfilledC,handleRejectedC);

// or, perhaps better ...

const myPromise =
  (new Promise(myExecutorFunc))
  .then(handleFulfilledA)
  .then(handleFulfilledB)
  .then(handleFulfilledC)
  .catch(handleRejectedAny);

拒否された promise の処理が早すぎると、promise の連鎖がさらに悪化します。エラーはすぐに処理する必要があるため、選択の余地がない場合があります。(結果を処理する手法については、以下の例の throw -999 を参照してください)。一方、緊急の必要がない場合は、最後の .catch() ステートメントまでエラー処理を省略した方が簡単です。

これらの2つの関数のシグネチャは単純で、任意のタイプの単一のパラメータを受け入れます。これらの関数は、プログラマによって作成されます。これらの関数の終了条件は、チェーン内の次のpromiseの「解決済み」状態を決定します。throw 以外の終了は「解決済み」状態を作成し、throw で終了するとrejected状態を作成します。

handleFulfilled(value)       { /*...*/; return nextValue;  }
handleRejection(reason)  { /*...*/; throw  nextReason; }
handleRejection(reason)  { /*...*/; return nextValue;  }

返される nextValue は別の promise オブジェクトにすることができます。その場合、promise は動的にチェーンに挿入されます。

.then() に適切な関数がない場合、処理はチェーンの次のリンクに続くだけです。したがって、チェーンは最後の .catch() まですべての handleRejection を安全に省略できます。同様に、.catch() は、実際には handleFulfilled のスロットがない .then() にすぎません。

チェーンの promise はロシアの人形のようにネストされていますが、スタックのトップのようにポップされます。 チェーンの最初の promise は最も深くネストされており、最初にポップされます。

(promise D, (promise C, (promise B, (promise A) ) ) )

nextValue が promise である場合、効果は動的な置換です。return により promise がポップされますが、nextValue promiseがその場所にプッシュされます。 上記のネストについて、「promise B」に関連付けられた .then() が「promise X」の nextValue を返すと仮定します。結果のネストは次のようになります。

(promise D, (promise C, (promise X) ) )

promise は複数のネストに参加できます。次のコードでは、promiseA が「解決済み」ステータスに移行すると、.then() の両方のインスタンスが呼び出されます。

const promiseA = new Promise(myExecutorFunc);
const promiseB = promiseA.then(handleFulfilled1, handleRejected1);
const promiseC = promiseA.then(handleFulfilled2, handleRejected2); 

アクションは、すでに「解決済み」のpromiseに割り当てることができます。その場合、アクションは (該当する場合) 最初の非同期の機会に実行されます。promise は非同期であることが保証されていることに注意してください。 したがって、すでに「解決済み」の promise に対するアクションは、スタックがクリアされてクロック刻みが経過した後にのみ発生します。その効果は、setTimeout(action,10) とほぼ同じです。

const promiseA = new Promise( (resolutionFunc,rejectionFunc) => {
    resolutionFunc(777);
});
// At this point, "promiseA" is already settled.
promiseA.then( (val) => console.log("asynchronous logging has val:",val) );
console.log("immediate logging");

// produces output in this order:
// immediate logging
// asynchronous logging has val: 777

Static メソッド

Promise.all(iterable)
すべての promise が解決されるか、いずれかの promise が拒否されるのを待ちます。
返された promise が解決される場合、複数の promise のイテラブルで定義されているのと同じ順序で、解決された promise からの値の集約配列で解決されます。
拒否された場合は、拒否されたイテラブルの最初の promise からの理由で拒否されます。
Promise.allSettled(iterable)
すべての promise が解決するまで待ちます(それぞれが解決または拒否する場合があります)。
指定されたすべての promise が解決または拒否された後に解決される promise を返します。それぞれの promise の結果を説明するオブジェクトの配列が含まれます。
Promise.race(iterable)
いずれかの promise が解決または拒否されるまで待ちます。
返された promise が解決されると、解決されたイテラブルの最初の promise の値で解決されます。
拒否された場合は、拒否された最初の promise からの理由で拒否されます。
Promise.reject(reason)
指定された理由で拒否された新しい Promise オブジェクトを返します。
Promise.resolve(value)
指定された値で解決される新しい Promise オブジェクトを返します。値が thenable である場合 (つまり then メソッドがある場合)、返された promise は、その thenable を「追跡」し、最終的な状態を採用します。それ以外の場合、返された promise は値で満たされます。
一般に、値が promise かどうかわからない場合は、代わりに Promise.resolve(value) を使用して、戻り値を promise として処理します。

Instance メソッド

Promise.prototype.catch()
拒否ハンドラーコールバックを promise に追加し、コールバックが呼び出された場合はコールバックの戻り値に解決する新しい promise を返します。promise が満たされた場合は元のフルフィルメント値に戻します。
Promise.prototype.then()
履行ハンドラと拒否ハンドラを promise に追加し、呼び出されたハンドラの戻り値に解決する新しい promise を返すか、promise が処理されなかった場合 (つまり、関連するハンドラ onFulfilled または onRejected が関数ではない場合) は元の解決された値に戻します。
Promise.prototype.finally()
ハンドラーを promise に追加し、元の promise が解決されたときに解決される新しい promise を返します。 ハンドラーは、promise が解決されたときに、実行されたか拒否されたかに関係なく呼び出されます。

基本的な使用例

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

いろいろな状況の例

以下の例は Promise の能力を使うためのさまざまなテクニックと、起こりうる状況を示します。これを理解するには、コードブロックの下までスクロールして、Promise チェーンをテストします。最初の Promise が与えられると、Promise チェーンが続きます。チェーンは .then() 呼び出しと、標準では (必須ではないですが) 終わりに .catch()と、.finally() が続きます。この例では、Promise チェーンは new Promise() で書かれたコンストラクタで初期化されます。実際の場合、Promise チェーンは Promise を返す(誰かが書いた) API 関数で始まることが普通です。

この例の tetheredGetNumber() 関数では、Promise生成箇所は非同期呼び出しやコールバック、またはその両方のセットアップに reject() を利用しています。promiseGetWord() 関数では、自身を含めるやり方で、どう API 関数が Promise を生成、返却しているかを示しています。

troubleWithGetNumber() 関数の最後に throw() があるのに気をつけてください。これは ES6 の Promise チェーンが、エラーの後でもすべての .then() Promise を通過し、"throw()"がなくてもエラーが"修正済"に見えるためです。これはややこしく、このために、.then()Promise を通して ejectionFunc を無視して、最後の catch() の中に 1 つの rejectionFunc を置くのが普通です。代替案は特別な値(ここでは "-999"ですが、カスタム Error 型がより適切です)を投げることです。

このコードは 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"));

応用例

Promise の仕組みを示したものです。testPromise() メソッドは <button> をクリックする度に呼び出されます。testPromise() メソッドは、window.setTimeout() を用いて、1秒から 3秒のランダムな時間の後、メソッドがこれまでに呼ばれた回数で成功する Promise を作成します。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 +
        ') Started (<small>Sync code started</small>)<br/>');

    // We make a new promise: we promise a numeric count of this promise, starting from 1 (after waiting 3s)
    let p1 = new Promise(
        // The executor function is called with the ability to resolve or
        // reject the promise
       (resolve, reject) => {
            log.insertAdjacentHTML('beforeend', thisPromiseCount +
                ') Promise started (<small>Async code started</small>)<br/>');
            // This is only an example to create asynchronism
            window.setTimeout(
                function() {
                    // We fulfill the promise !
                    resolve(thisPromiseCount);
                }, Math.random() * 2000 + 1000);
        }
    );

    // We define what to do when the promise is resolved with the then() call,
    // and what to do when the promise is rejected with the catch() call
    p1.then(
        // Log the fulfillment value
        function(val) {
            log.insertAdjacentHTML('beforeend', val +
                ') Promise fulfilled (<small>Async code terminated</small>)<br/>');
        }).catch(
        // Log the rejection reason
       (reason) => {
            console.log('Handle rejected promise ('+reason+') here.');
        });

    log.insertAdjacentHTML('beforeend', thisPromiseCount +
        ') Promise made (<small>Sync code terminated</small>)<br/>');
}

この例はボタンをクリックすると実行されます。(ブラウザーが Promise に対応している必要があります。)

短い時間の間に何度かボタンをクリックすると、それぞれの promise が次々と成功するのがわかります。

XHR による画像の読み込み

PromiseXMLHttpRequest で画像を読み込む別の例は、MDN GitHub js-examples リポジトリにあり、動作を確認することができます。それぞれの行のコメントで Promise と XHR の構造がよくわかるはずです。

仕様

仕様書
ECMAScript (ECMA-262)
Promise の定義

ブラウザの互換性

Update compatibility data on GitHub
デスクトップモバイルサーバー
ChromeEdgeFirefoxInternet ExplorerOperaSafariAndroid webviewAndroid 版 ChromeAndroid 版 FirefoxAndroid 版 OperaiOSのSafariSamsung InternetNode.js
PromiseChrome 完全対応 32Edge 完全対応 12Firefox 完全対応 29IE 未対応 なしOpera 完全対応 19Safari 完全対応 8WebView Android 完全対応 4.4.3Chrome Android 完全対応 32Firefox Android 完全対応 29Opera Android 完全対応 19Safari iOS 完全対応 8Samsung Internet Android 完全対応 2.0nodejs 完全対応 0.12
Promise() constructorChrome 完全対応 32Edge 完全対応 12Firefox 完全対応 29
補足
完全対応 29
補足
補足 Constructor requires a new operator since version 37.
IE 未対応 なしOpera 完全対応 19Safari 完全対応 8
補足
完全対応 8
補足
補足 Constructor requires a new operator since version 10.
WebView Android 完全対応 4.4.3Chrome Android 完全対応 32Firefox Android 完全対応 29
補足
完全対応 29
補足
補足 Constructor requires a new operator since version 37.
Opera Android 完全対応 19Safari iOS 完全対応 8
補足
完全対応 8
補足
補足 Constructor requires a new operator since version 10.
Samsung Internet Android 完全対応 2.0nodejs 完全対応 0.12
補足
完全対応 0.12
補足
補足 Constructor requires a new operator since version 4.
all()Chrome 完全対応 32Edge 完全対応 12Firefox 完全対応 29IE 未対応 なしOpera 完全対応 19Safari 完全対応 8WebView Android 完全対応 4.4.3Chrome Android 完全対応 32Firefox Android 完全対応 29Opera Android 完全対応 19Safari iOS 完全対応 8Samsung Internet Android 完全対応 2.0nodejs 完全対応 0.12
allSettled()Chrome 完全対応 76Edge 完全対応 79Firefox 完全対応 71IE 未対応 なしOpera 完全対応 63Safari 完全対応 13WebView Android 完全対応 76Chrome Android 完全対応 76Firefox Android 未対応 なしOpera Android 完全対応 54Safari iOS 完全対応 13Samsung Internet Android 未対応 なしnodejs 完全対応 12.9.0
catch()Chrome 完全対応 32Edge 完全対応 12Firefox 完全対応 29IE 未対応 なしOpera 完全対応 19Safari 完全対応 8WebView Android 完全対応 4.4.3Chrome Android 完全対応 32Firefox Android 完全対応 29Opera Android 完全対応 19Safari iOS 完全対応 8Samsung Internet Android 完全対応 2.0nodejs 完全対応 0.12
finally()Chrome 完全対応 63Edge 完全対応 18Firefox 完全対応 58IE 未対応 なしOpera 完全対応 50Safari 完全対応 11.1WebView Android 完全対応 63Chrome Android 完全対応 63Firefox Android 完全対応 58Opera Android 完全対応 46Safari iOS 完全対応 11.3Samsung Internet Android 完全対応 8.0nodejs 完全対応 10.0.0
race()Chrome 完全対応 32Edge 完全対応 12Firefox 完全対応 29IE 未対応 なしOpera 完全対応 19Safari 完全対応 8WebView Android 完全対応 4.4.3Chrome Android 完全対応 32Firefox Android 完全対応 29Opera Android 完全対応 19Safari iOS 完全対応 8Samsung Internet Android 完全対応 2.0nodejs 完全対応 0.12
reject()Chrome 完全対応 32Edge 完全対応 12Firefox 完全対応 29IE 未対応 なしOpera 完全対応 19Safari 完全対応 8WebView Android 完全対応 4.4.3Chrome Android 完全対応 32Firefox Android 完全対応 29Opera Android 完全対応 19Safari iOS 完全対応 8Samsung Internet Android 完全対応 2.0nodejs 完全対応 0.12
resolve()Chrome 完全対応 32Edge 完全対応 12Firefox 完全対応 29IE 未対応 なしOpera 完全対応 19Safari 完全対応 8WebView Android 完全対応 4.4.3Chrome Android 完全対応 32Firefox Android 完全対応 29Opera Android 完全対応 19Safari iOS 完全対応 8Samsung Internet Android 完全対応 2.0nodejs 完全対応 0.12
then()Chrome 完全対応 32Edge 完全対応 12Firefox 完全対応 29IE 未対応 なしOpera 完全対応 19Safari 完全対応 8WebView Android 完全対応 4.4.3Chrome Android 完全対応 32Firefox Android 完全対応 29Opera Android 完全対応 19Safari iOS 完全対応 8Samsung Internet Android 完全対応 2.0nodejs 完全対応 0.12

凡例

完全対応  
完全対応
未対応  
未対応
実装ノートを参照してください。
実装ノートを参照してください。

関連情報