JavaScript の Promise: 概要

Promise は、遅延計算と非同期計算を簡素化します。Promise は、まだ完了していないオペレーションを表します。

デベロッパーは、Google Cloud の歴史における重要な瞬間に備えましょう ウェブ開発

[ドラムロールの開始]

JavaScript に Promise が加わりました。

[花火が爆発し、キラキラした紙の雨が上方から降り、観客は盛り上がる]

この時点で、次のいずれかのカテゴリに分類できます。

  • みんながあなたの周りを応援しています。でも、何が騒がれているのかはわかりません。 表示されます。もしかしたら、あなたはこの言葉の「Promise」があります肩をすくめますが、 キラキラした紙の重さがあなたの肩にかかっています。その場合は、 なぜそれが気に入ったのかわかってくれると、何年もかかりました。 ありません。最初から始めることをおすすめします。
  • パンチしたね!そろそろね。以前に Promise を使用していたことがあります 実装ごとに API が若干異なることに注意が必要です。 公式 JavaScript バージョンの API を教えてください。最初は 用語をご覧ください。
  • あなたはすでにこれを知っていて、飛び跳ねる人たちを嘲笑します。 とてもニュースのように感じます自分自身の優位性をたたえましょう。 API リファレンスに進みます。

ブラウザのサポートとポリフィル

対応ブラウザ

  • Chrome: 32。 <ph type="x-smartling-placeholder">
  • Edge: 12。 <ph type="x-smartling-placeholder">
  • Firefox: 29。 <ph type="x-smartling-placeholder">
  • Safari: 8. <ph type="x-smartling-placeholder">

ソース

完全な Promise がないブラウザを仕様どおりに実装する 他のブラウザや Node.js に Promise を追加する方法については、 ポリフィル (2k gzip 圧縮)。

何が問題なの?

JavaScript はシングル スレッドなので、2 つのスクリプトを できます。次々と実行する必要があります。ブラウザでは、JavaScript は スレッドを共有しているスレッドは他にもたくさんあり、ブラウザや できます。ただし、通常 JavaScript は描画、更新と同じキューにある ユーザーの操作(テキストのハイライト表示や操作など)の処理 。これらのうちの 1 つの活動が、他の活動を遅らせる。

人間はマルチスレッドです。複数の指で入力したり 運転しながら会話を進めることができます。唯一の阻害要因は くしゃみをして、現在のすべての活動が くしゃみしている間は一時停止します。かなりイライラします。 運転中や会話するときは特にそうです。あなたは 悪意のあるコードを書きたい場合もあるでしょう。

これを回避するために、イベントやコールバックを使用したことがあるかもしれません。イベントは次のとおりです。

var img1 = document.querySelector('.img-1');

img1.addEventListener('load', function() {
  // woo yey image loaded
});

img1.addEventListener('error', function() {
  // argh everything's broken
});

これはまったくくしゃみではない。画像を取得していくつかのリスナーを追加し、 JavaScript は、いずれかのリスナーが呼び出されるまで実行を停止できます。

残念ながら、上記の例では、イベントが発生した可能性があります その問題に対処する必要があります 「完了」プロパティの一例です。

var img1 = document.querySelector('.img-1');

function loaded() {
  // woo yey image loaded
}

if (img1.complete) {
  loaded();
}
else {
  img1.addEventListener('load', loaded);
}

img1.addEventListener('error', function() {
  // argh everything's broken
});

これにより、リッスンできるようになる前にエラーが発生した画像は検出されません。 them;残念ながら DOM にはそのための手段がありません。また、これは 1 つの画像を読み込んでいます。セットになったタイミングを知りたければ、状況はさらに複雑になります。 の画像を読み込みました。

イベントは必ずしも

同じイベントで複数回発生する可能性のあるイベントに効果的です オブジェクト(keyuptouchstart など)。これらのイベントでは特に重要ではない リスナーをアタッチする前に何が発生したかがわかります。ただし、 理想的には、次のようなものが必要です。

img1.callThisIfLoadedOrWhenLoaded(function() {
  // loaded
}).orIfFailedCallThis(function() {
  // failed
});

// and…
whenAllTheseHaveLoaded([img1, img2]).callThis(function() {
  // all loaded
}).orIfSomeFailedCallThis(function() {
  // one or more failed
});

これが Promise の機能ですが、名前が改善されています。HTML 画像要素に 「準備完了」Promise を返したメソッドの場合、次のことができます。

img1.ready()
.then(function() {
  // loaded
}, function() {
  // failed
});

// and…
Promise.all([img1.ready(), img2.ready()])
.then(function() {
  // all loaded
}, function() {
  // one or more failed
});

最も基本的な点として、Promise はイベント リスナーに少し似ていますが、次の点が異なります。

  • Promise は 1 回だけ成功または失敗します。2 回成功したり失敗したりすることはできないので、 成功から失敗(またはその逆)に切り替えることができます。
  • Promise が成功または失敗した後、後で成功/失敗を追加する場合 イベントがトリガーされても、正しいコールバックが呼び出されます。 おすすめします

非同期に成功/失敗する場合に非常に便利です。なぜなら、 何かが入手可能になった正確な時間に関心があり、 モデルに与える影響です

Promise の用語

Domenic Denicola の証明が最初の下書きを読みました 評価して「F」と評価しました。ご覧ください。彼は私を拘束に入れ、 手動でコピーして 状態と運命 親に心配する手紙を書きました。それにもかかわらず、私は 多くの用語が混同されていますが、基本的なものは次のとおりです。

Promise には次のようなものがあります。

  • fulfilled - Promise に関連するアクションが成功した
  • rejected - Promise に関連するアクションが失敗した
  • 保留中 - まだ処理も不承認もされていません
  • 決済済み - 履行または不承認

仕様 また、Promise に似たオブジェクトを指す thenable という用語も使用されています。 then メソッドがある点です。この言葉は元イングランド フットボールを連想させる 管理者の Terry Venables には、 できるだけ使用頻度を少なくします。

Promise が JavaScript に実装されます。

Promise は以前から、次のようなライブラリの形で使用されていました。

上記の Promise と JavaScript の Promise は、共通の標準化された動作を持ちます。 Promise/A+ を使用します。条件 jQuery ユーザーであれば 遅延。ただし、 遅延は Promise/A+ に準拠していないため、遅延が発生します。 若干異なり ご注意ください。jQuery には Promise タイプですが、これは単なる Deferred のサブセットであり、同じ問題があります。

Promise の実装は標準化された動作に従いますが、 場合によって違いがあります。JavaScript の Promise は API の RSVP.js と同様です。 Promise の作成方法は次のとおりです。

var promise = new Promise(function(resolve, reject) {
  // do a thing, possibly async, then…

  if (/* everything turned out fine */) {
    resolve("Stuff worked!");
  }
  else {
    reject(Error("It broke"));
  }
});

Promise コンストラクタは引数を 1 つ受け取ります。コールバックには 2 つのパラメータ、 拒否できます。コールバック内でなんらかの処理(非同期など)を行ってから、 すべてが正常に機能した場合は解決し、それ以外の場合は呼び出しを拒否します。

単純で古い JavaScript の throw と同様に、慣例的ですが必須ではありません。 エラー オブジェクトで拒否されます。Error オブジェクトのメリットは、 デバッグツールがより便利になります。

この Promise の使用方法は次のとおりです。

promise.then(function(result) {
  console.log(result); // "Stuff worked!"
}, function(err) {
  console.log(err); // Error: "It broke"
});

then() は 2 つの引数を受け取ります。成功した場合のコールバックと、 割り当てますどちらも省略可能であるため、アプリケーションのコールバックを 成功または失敗のケースのみ 対象になります

JavaScript の Promise は DOM で当初は「Futures」と呼ばれ、「Promises」に名前が変更されましたが、 最後に JavaScript に移行しましたコードではなく JavaScript で記述すれば、 DOM は、 Node.js(コア API で使用するかどうかは別の問題)

これらの機能は JavaScript の機能ですが、DOM では問題なく使用できます。イン 実際、非同期の成功/失敗メソッドを使用するすべての新しい DOM API で、Promise が使用されます。 これはすでに 割り当て管理 フォント読み込みイベント ServiceWorkerWeb MIDIストリームなど。

他のライブラリとの互換性

JavaScript Promise API は、then() メソッドを含むすべてのものを次のように処理します。 Promise に似ています(または、Promise の話す「ため息」thenable)。したがって、ライブラリを使用している場合、 Q Promise を返すことができますが、問題ありません。新しい Promise で JavaScript Promise。

ただし、先ほども説明したように、jQuery の Deferred は少し役に立ちません。 幸いなことに、これらを標準の Promise にキャストできます。これは、試してみる価値があります。 「できるだけ早く対応してください」:

var jsPromise = Promise.resolve($.ajax('/whatever.json'))

ここで、jQuery の $.ajax は Deferred を返します。then() メソッドが含まれるため、 Promise.resolve() は、これを JavaScript の Promise に変換できます。ただし、 deferred はコールバックに複数の引数を渡すことがあります。次に例を示します。

var jqDeferred = $.ajax('/whatever.json');

jqDeferred.then(function(response, statusText, xhrObj) {
  // ...
}, function(xhrObj, textStatus, err) {
  // ...
})

一方、JS Promise は最初のもの以外をすべて無視します。

jsPromise.then(function(response) {
  // ...
}, function(xhrObj) {
  // ...
})

幸いなことに、通常はこれだけで十分ですが、少なくとも できます。また、jQuery は Error オブジェクトを拒否対象に渡すことができます。

複雑な非同期コードが簡単に

コーディングに取り組みましょう次のようにしたいとします。

  1. 読み込み中であることを示すスピナーを開始する
  2. 記事の JSON を取得して、各章のタイトルと URL を取得する
  3. ページにタイトルを追加する
  4. 各チャプターを取得する
  5. ページにストーリーを追加する
  6. スピナーを停止します

途中で何か問題が発生した場合でも、ユーザーに知らせます。ここで、 スピナーもその時点で停止します 他の UI にクラッシュする

ストーリーの配信に JavaScript は使用しません より高速です API を扱う際は、このパターンがよく見られます。つまり、複数のデータを 完了したらなんらかの処理を行います

まず、ネットワークからデータを取得してみましょう。

XMLHttpRequest の確約利用

過去にさかのぼって可能であれば、Promise を使用するように古い API が更新される あります。XMLHttpRequest が最有力候補ですが、間もなくです GET リクエストを行うシンプルな関数を記述しましょう。

function get(url) {
  // Return a new promise.
  return new Promise(function(resolve, reject) {
    // Do the usual XHR stuff
    var req = new XMLHttpRequest();
    req.open('GET', url);

    req.onload = function() {
      // This is called even on 404 etc
      // so check the status
      if (req.status == 200) {
        // Resolve the promise with the response text
        resolve(req.response);
      }
      else {
        // Otherwise reject with the status text
        // which will hopefully be a meaningful error
        reject(Error(req.statusText));
      }
    };

    // Handle network errors
    req.onerror = function() {
      reject(Error("Network Error"));
    };

    // Make the request
    req.send();
  });
}

これを使ってみましょう。

get('story.json').then(function(response) {
  console.log("Success!", response);
}, function(error) {
  console.error("Failed!", error);
})

これで、XMLHttpRequest を手動で入力せずに HTTP リクエストを送信できるようになりました。 XMLHttpRequest の激怒するキャメルケースを見る必要がないほど、私の人生はもっと幸せになります。

チェーン

then() でストーリーは終了しません。then を連結すると、 追加の非同期アクションを次々と実行することもできます。

値の変換

新しい値を返すだけで、値を変換できます。

var promise = new Promise(function(resolve, reject) {
  resolve(1);
});

promise.then(function(val) {
  console.log(val); // 1
  return val + 2;
}).then(function(val) {
  console.log(val); // 3
})

実用的な例として、次の画面に戻りましょう。

get('story.json').then(function(response) {
  console.log("Success!", response);
})

レスポンスは JSON ですが、現在は書式なしテキストとして受け取っています。水 JSON を使用して get 関数を変更すると、 responseType Promise の領域でも解決できます。

get('story.json').then(function(response) {
  return JSON.parse(response);
}).then(function(response) {
  console.log("Yey JSON!", response);
})

JSON.parse() は単一の引数を取り、変換された値を返すため、 次のようなショートカットを作成できます。

get('story.json').then(JSON.parse).then(function(response) {
  console.log("Yey JSON!", response);
})

実際、getJSON() 関数はとても簡単に作成できます。

function getJSON(url) {
  return get(url).then(JSON.parse);
}

getJSON() は引き続き Promise を返します。Promise は URL を取得して解析し、 JSON 形式で返されます。

非同期アクションをキューに入れる

then を連結して、非同期アクションを順番に実行することもできます。

then() コールバックから何かを返す場合、これは魔法のようなものです。 値を返すと、その値を使用して次の then() が呼び出されます。ただし、 Promise のようなものが返された場合、次の then() はそれに対して待機し、 その Promise が解決(成功または失敗)した場合にのみ呼び出されます。例:

getJSON('story.json').then(function(story) {
  return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
  console.log("Got chapter 1!", chapter1);
})

ここでは、story.json に対して非同期リクエストを行います。これにより、次の一連の 最初の URL をリクエストします。この時点で 単純なコールバックパターンから目立ちます

チャプターを取得するショートカット メソッドを作成することもできます。

var storyPromise;

function getChapter(i) {
  storyPromise = storyPromise || getJSON('story.json');

  return storyPromise.then(function(story) {
    return getJSON(story.chapterUrls[i]);
  })
}

// and using it is simple:
getChapter(0).then(function(chapter) {
  console.log(chapter);
  return getChapter(1);
}).then(function(chapter) {
  console.log(chapter);
})

getChapter が呼び出されるまで story.json はダウンロードされませんが、 getChapter というストーリーの Promise を再利用するため、story.json 取得されるのは 1 回だけです。さあ、約束です!

エラー処理

先ほど見たように、then() は 2 つの引数を取ります。1 つは成功用、もう 1 つは成功用です。 (Promise の用語では、履行と拒否):

get('story.json').then(function(response) {
  console.log("Success!", response);
}, function(error) {
  console.log("Failed!", error);
})

catch() を使用することもできます。

get('story.json').then(function(response) {
  console.log("Success!", response);
}).catch(function(error) {
  console.log("Failed!", error);
})

catch()は特に何もなく、ただ砂糖だけ then(undefined, func) ですが、読みやすくなっています。2 つのコードが 上記の例は同じようには動作しませんが、後者は以下と同等です。

get('story.json').then(function(response) {
  console.log("Success!", response);
}).then(undefined, function(error) {
  console.log("Failed!", error);
})

わずかな違いですが、非常に便利です。Promise の拒否のスキップ 拒否のコールバックで次のthen()catch() 同等です)。then(func1, func2) では、func1 または func2 が次のようになります。 両方は呼び出されません。しかし then(func1).catch(func2) があれば、どちらも これらはチェーン内の個別のステップであるため、func1 が拒否された場合に呼び出されます。乗る 次のとおりです。

asyncThing1().then(function() {
  return asyncThing2();
}).then(function() {
  return asyncThing3();
}).catch(function(err) {
  return asyncRecovery1();
}).then(function() {
  return asyncThing4();
}, function(err) {
  return asyncRecovery2();
}).catch(function(err) {
  console.log("Don't worry about it");
}).then(function() {
  console.log("All done!");
})

上記のフローは、通常の JavaScript try/catch エラーと非常によく似ています。 「try」の範囲内ですぐに catch() ブロックに移ります。こちらが 上の図はフローチャートです(私はフローチャートが大好きなので)。

履行される Promise については青い線を、履行される Promise については赤い線に従ってください 拒否されます。

JavaScript の例外と Promise

拒否は、Promise が明示的に拒否された場合にも、暗黙的に拒否された場合にも発生します コンストラクタ コールバックでエラーがスローされた場合:

var jsonPromise = new Promise(function(resolve, reject) {
  // JSON.parse throws an error if you feed it some
  // invalid JSON, so this implicitly rejects:
  resolve(JSON.parse("This ain't JSON"));
});

jsonPromise.then(function(data) {
  // This never happens:
  console.log("It worked!", data);
}).catch(function(err) {
  // Instead, this happens:
  console.log("It failed!", err);
})

つまり、Promise 関連のすべての処理を Promise コンストラクタ コールバックが使用されるため、エラーは自動的にキャッチされ、 不承認となります。

then() コールバックでスローされるエラーについても同様です。

get('/').then(JSON.parse).then(function() {
  // This never happens, '/' is an HTML page, not JSON
  // so JSON.parse throws
  console.log("It worked!", data);
}).catch(function(err) {
  // Instead, this happens:
  console.log("It failed!", err);
})

エラー処理の実例

ストーリーとチャプターでは、catch を使用してユーザーにエラーを表示できます。

getJSON('story.json').then(function(story) {
  return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
  addHtmlToPage(chapter1.html);
}).catch(function() {
  addTextToPage("Failed to show chapter");
}).then(function() {
  document.querySelector('.spinner').style.display = 'none';
})

story.chapterUrls[0] の取得に失敗した場合(HTTP 500、ユーザーがオフラインの場合など)は、 後続の成功コールバックはすべてスキップされます。これには getJSON() は、レスポンスを JSON として解析しようとします。また、 ページに chapter1.html を追加するコールバックを指定します。代わりに、catching に移る 呼び出すことができます。その結果、「Failed to show cha」(チャプターを表示できませんでした)が発生します。次の場合にページに追加されます 前の操作のいずれかが失敗しました

JavaScript の try/catch と同様に、エラーがキャッチされ、後続のコードが スピナーは常に非表示になります「 は、次の非ブロック非同期バージョンになります。

try {
  var story = getJSONSync('story.json');
  var chapter1 = getJSONSync(story.chapterUrls[0]);
  addHtmlToPage(chapter1.html);
}
catch (e) {
  addTextToPage("Failed to show chapter");
}
document.querySelector('.spinner').style.display = 'none'

ログを記録するためだけに、復元せずに catch() を行うこともできます。 確認できます。これを行うには、エラーを再スローします。これは getJSON() メソッドを使用します。

function getJSON(url) {
  return get(url).then(JSON.parse).catch(function(err) {
    console.log("getJSON failed for", url, err);
    throw err;
  });
}

これで 1 つの章を取得できましたが、すべて取得する必要があります。さっそく できます。

並列処理とシーケンシング: 両方を活用する

非同期を考えることは簡単ではありません。なかなか難しいのであれば、 同期しているようにコードを記述してみてください。次のような場合があります。

try {
  var story = getJSONSync('story.json');
  addHtmlToPage(story.heading);

  story.chapterUrls.forEach(function(chapterUrl) {
    var chapter = getJSONSync(chapterUrl);
    addHtmlToPage(chapter.html);
  });

  addTextToPage("All done");
}
catch (err) {
  addTextToPage("Argh, broken: " + err.message);
}

document.querySelector('.spinner').style.display = 'none'

問題ありません。ダウンロード中はブラウザが同期され、ロックされます。宛先 これを非同期にするため、then() を使用して次々と処理を行います。

getJSON('story.json').then(function(story) {
  addHtmlToPage(story.heading);

  // TODO: for each url in story.chapterUrls, fetch &amp; display
}).then(function() {
  // And we're all done!
  addTextToPage("All done");
}).catch(function(err) {
  // Catch any error that happened along the way
  addTextToPage("Argh, broken: " + err.message);
}).then(function() {
  // Always hide the spinner
  document.querySelector('.spinner').style.display = 'none';
})

しかし、チャプター URL をループして順番に取得するにはどうすればよいでしょうか。この 動作しない場合:

story.chapterUrls.forEach(function(chapterUrl) {
  // Fetch chapter
  getJSON(chapterUrl).then(function(chapter) {
    // and add it to the page
    addHtmlToPage(chapter.html);
  });
})

forEach は非同期に対応していないため、チャプターはどの順序でも表示されます。 映画『パルプ・フィクション』の原稿の書き方ですこれは違います。 『パルプ・フィクション』です。修正しましょう。

シーケンスを作成する

chapterUrls 配列を一連の Promise に変換する必要があります。そのためには、then() を使用します。

// Start off with a promise that always resolves
var sequence = Promise.resolve();

// Loop through our chapter urls
story.chapterUrls.forEach(function(chapterUrl) {
  // Add these actions to the end of the sequence
  sequence = sequence.then(function() {
    return getJSON(chapterUrl);
  }).then(function(chapter) {
    addHtmlToPage(chapter.html);
  });
})

Promise.resolve() が出現するのは今回が初めてです。 指定した値に解決されるという Promise があります。ルールに Promise のインスタンスを返すと、そのインスタンスは単にそれを返します(注: これは 実装がまだ準拠していない仕様に変更されます。もし (then() メソッドがある)Promise のようなものを渡すと、 同じ方法で解決または拒否する本物の Promise。合格した場合 その他の値(例:Promise.resolve('Hello') の場合、 その値で履行される Promise を返します。値を指定せずに呼び出すと 「未定義」で履行されます。

また、Promise.reject(val) もあります。これは、拒否する Promise を作成します。 (または未定義)。

次のコマンドで上記のコードを整理できます。 array.reduce:

// Loop through our chapter urls
story.chapterUrls.reduce(function(sequence, chapterUrl) {
  // Add these actions to the end of the sequence
  return sequence.then(function() {
    return getJSON(chapterUrl);
  }).then(function(chapter) {
    addHtmlToPage(chapter.html);
  });
}, Promise.resolve())

これは前の例と同じですが、個別の 「シーケンス」変数です。削減コールバックは配列内の各アイテムに対して呼び出されます。 「シーケンス」最初は Promise.resolve() ですが、それ以降は 「シーケンス」を呼び出す前の呼び出しで返された値になりますarray.reduce は配列を単一の値に集約する場合に非常に便利です。この例では、 約束です。

まとめると、次のようになります。

getJSON('story.json').then(function(story) {
  addHtmlToPage(story.heading);

  return story.chapterUrls.reduce(function(sequence, chapterUrl) {
    // Once the last chapter's promise is done…
    return sequence.then(function() {
      // …fetch the next chapter
      return getJSON(chapterUrl);
    }).then(function(chapter) {
      // and add it to the page
      addHtmlToPage(chapter.html);
    });
  }, Promise.resolve());
}).then(function() {
  // And we're all done!
  addTextToPage("All done");
}).catch(function(err) {
  // Catch any error that happened along the way
  addTextToPage("Argh, broken: " + err.message);
}).then(function() {
  // Always hide the spinner
  document.querySelector('.spinner').style.display = 'none';
})

これで、同期バージョンの完全非同期バージョンが完成しました。しかし、私たちには 向上します現在、ページは次のようにダウンロードされています。

ブラウザは一度に複数のコンテンツをダウンロードするのに適しているため、 チャプターを 1 つずつ順にダウンロードすることでパフォーマンスが向上します。目標は すべて同時にダウンロードしてから、すべての到着時に処理することができます。 幸い、これを行うための API があります。

Promise.all(arrayOfPromises).then(function(arrayOfResults) {
  //...
})

Promise.all は、Promise の配列を受け取り、次の条件を満たす Promise を作成します。 すべて完了すると予測されます結果の配列(任意の文字列)が Promise の履行)を渡した Promise と同じ順序で処理します。

getJSON('story.json').then(function(story) {
  addHtmlToPage(story.heading);

  // Take an array of promises and wait on them all
  return Promise.all(
    // Map our array of chapter urls to
    // an array of chapter json promises
    story.chapterUrls.map(getJSON)
  );
}).then(function(chapters) {
  // Now we have the chapters jsons in order! Loop through…
  chapters.forEach(function(chapter) {
    // …and add to the page
    addHtmlToPage(chapter.html);
  });
  addTextToPage("All done");
}).catch(function(err) {
  // catch any error that happened so far
  addTextToPage("Argh, broken: " + err.message);
}).then(function() {
  document.querySelector('.spinner').style.display = 'none';
})

接続によっては、これを 1 つずつ読み込むよりも数秒速くなる場合があり、 1 回目よりコードが少なくて済みますどのチャプターがどの形式でも 画面上には正しい順序で表示されます

ただし、知覚されるパフォーマンスは改善できます。第 1 章が進むと 自動的にページに追加されますこれにより、ユーザーは次の内容に進む前に読み始めることができます。 始まりです。第 3 章に到達した時点で、第 3 章には追加せず、 第 2 章が欠落していることにユーザーが気付いていない可能性があるためです。第 2 章では、 第 2 章と第 3 章を追加できます

そのためには、すべてのチャプターの JSON を同時に取得してから、 ドキュメントに追加します。

getJSON('story.json')
.then(function(story) {
  addHtmlToPage(story.heading);

  // Map our array of chapter urls to
  // an array of chapter json promises.
  // This makes sure they all download in parallel.
  return story.chapterUrls.map(getJSON)
    .reduce(function(sequence, chapterPromise) {
      // Use reduce to chain the promises together,
      // adding content to the page for each chapter
      return sequence
      .then(function() {
        // Wait for everything in the sequence so far,
        // then wait for this chapter to arrive.
        return chapterPromise;
      }).then(function(chapter) {
        addHtmlToPage(chapter.html);
      });
    }, Promise.resolve());
}).then(function() {
  addTextToPage("All done");
}).catch(function(err) {
  // catch any error that happened along the way
  addTextToPage("Argh, broken: " + err.message);
}).then(function() {
  document.querySelector('.spinner').style.display = 'none';
})

では、両方の長所を見ていきましょう。同じくらい時間がかかるのは コンテンツの最初の部分がすぐに表示されます。

この簡単な例では、すべての章がほぼ同時に到着しますが、 一度に 1 つずつ表示することのメリットは、 構成されます。

これは、Node.js スタイルのコールバックまたは events は、この地域の 難しくなりますただし、 他の ES6 機能と組み合わせると、Promise はこれで終わりではありません。 さらに簡単になります

ボーナス ラウンド: 拡張機能

最初にこの記事を執筆して以来、Promise を使用できる機能は拡張されました。 非常に困難です。Chrome 55 以降、非同期関数では Promise ベースのコードに対して 同期であるかのように記述しますが、メインスレッドをブロックしません。Google Chat では 詳しくは、非同期関数に関する記事をご覧ください。また、 主要なブラウザで、Promise と非同期関数の両方が幅広くサポートされるようになりました。 詳しくは MDN の 約束 および async 関数 ご覧ください。

Anne van Kesteren、Domenic Denicola、Tom Ashworth、Remy Sharp に心より感謝いたします。 Addy Osmani、Arthur Evans、Yutaka Hirano の 3 人は、 提案することもあります

加えて、Mathias Bynens ご覧ください。