2022年2月19日 非同期処理:コールバック/Promise/Async - Promiseチェーンで逐次処理 (JavaScript Primer)
Promiseチェーンで逐次処理
Promiseチェーンで非同期処理の流れを書く大きなメリットは、非同期処理のさまざまなパターンに対応できることです。
ここでは、典型的な例として複数の非同期処理を順番に処理していく逐次処理を考えていきましょう。 Promiseで逐次的な処理といっても難しいことはなく、単純にthenで非同期処理をつないでいくだけです。
以下はdummyFetchを実行してif文がtrueとなりthenメソッドで配列にresponse.bodyを追加した後に、dummyFetchを再度実行している例です。
(逐次処理)
Resource AとResource Bを順番に取得しています。 それぞれ取得したリソースを変数resultsに追加し、すべて取得し終わったらコンソールに出力します。
function dummyFetch(path) { return new Promise((resolve, reject) => { setTimeout(() => { if (path.startsWith("/resource")) { resolve({ body: `Response body of ${path}` }); } else { reject(new Error("NOT FOUND")); } }, 1000 * Math.random()); }); } const results = []; // Resource Aを取得する dummyFetch("/resource/A").then(response => { results.push(response.body); // Resource Bを取得する return dummyFetch("/resource/B"); }).then(response => { results.push(response.body); }).then(() => { console.log(results); // => ["Response body of /resource/A", "Response body of /resource/B"] });
しかし、Resource AとBどちらを先に取得しても問題ない場合は、Promise.allメソッドを使って複数のPromiseを1つのPromiseとしてまとめられます。
Promise.allで複数のPromiseをまとめる
Promise.allを使うことで複数のPromiseを使った非同期処理をひとつのPromiseとして扱えます。
Promise.allメソッドは Promiseインスタンスの配列を受け取り、新しいPromiseインスタンスを返します。 その配列のすべてのPromiseインスタンスがFulfilledとなった場合は、返り値のPromiseインスタンスもFulfilledとなります。 一方で、ひとつでもRejectedとなった場合は、返り値のPromiseインスタンスもRejectedとなります。
Promise.allメソッド
- インスタンス化して配列を返します。
- 配列の返り値が全てFulfilledの場合のみFulfilledで返します。
- 配列の返り値が一つでもRejectedの場合はRejectedを返します。
// `timeoutMs`ミリ秒後にresolveする function delay(timeoutMs) { return new Promise((resolve) => { setTimeout(() => { resolve(timeoutMs); }, timeoutMs); }); } const promise1 = delay(1); const promise2 = delay(2); const promise3 = delay(3); Promise.all([promise1, promise2, promise3]).then(function(values) { console.log(values); // => [1, 2, 3] });
先ほどのPromiseチェーンでリソースを取得する例では、Resource Aを取得し終わってからResource Bを取得というように逐次的でした。 しかし、Resource AとBどちらを先に取得しても問題ない場合は、Promise.allメソッドを使って複数のPromiseを1つのPromiseとしてまとめられます。
メリットとして、 Resource AとBを同時に取得すればより早い時間で処理が完了します。
次のコードでは、Resource AとBを同時に取得開始しています。 両方のリソースの取得が完了すると、thenのコールバック関数にはAとBの結果が配列として渡されます。
- 順番が関係のない場合は一度にPromise.allメソッドで複数のPromiseを一つにまとめられます。
- より早く処理が終わります。
- 同時に取得するとthenのコールバック関数の引数に返り値が分割代入されます。
function dummyFetch(path) { return new Promise((resolve, reject) => { setTimeout(() => { if (path.startsWith("/resource")) { resolve({ body: `Response body of ${path}` }); } else { reject(new Error("NOT FOUND")); } }, 1000 * Math.random()); }); } const fetchedPromise = Promise.all([ dummyFetch("/resource/A"), dummyFetch("/resource/B") ]); // fetchedPromiseの結果をDestructuringでresponseA, responseBに代入している fetchedPromise.then(([responseA, responseB]) => { console.log(responseA.body); // => "Response body of /resource/A" console.log(responseB.body); // => "Response body of /resource/B" });
先述したように渡したPromiseがひとつでもRejectedとなった場合は、失敗時の処理が呼び出される事の検証結果です。
function dummyFetch(path) { return new Promise((resolve, reject) => { setTimeout(() => { if (path.startsWith("/resource")) { resolve({ body: `Response body of ${path}` }); } else { reject(new Error("NOT FOUND")); } }, 1000 * Math.random()); }); } const fetchedPromise = Promise.all([ dummyFetch("/resource/A"), dummyFetch("/not_found/B") // Bは存在しないため失敗する ]); fetchedPromise.then(([responseA, responseB]) => { // この行は実行されません }).catch(error => { console.error(error); // Error: NOT FOUND });
- PromiseState内部プロパティがRejectedで返ってきているのでthenメソッドが無視されcatchの処理に移行しています。
復習
分割代入
ある特定の情報だけを参照して、変数に格納したい場合、分割代入を使います。 影響範囲を少なくするため、変数に格納して使用します。
配列の場合
右辺の配列の各要素を、左辺の配列の各要素に代入します。
const array = [1, 2]; // aには`array`の0番目の値、bには1番目の値が代入されます(分割代入)。 const [a, b] = array; console.log(a); // => 1 console.log(b); // => 2
まとめ
- Promise.allを使う事で複数の非同期処理をまとめる事ができます。
- 複数の非同期処理を逐次的に行う場合、順番を意識しないのであればPromise.Allを使って同時に行うことで、処理を効率化することができる。