Promise
クラスには 6 つの静的メソッドがあります。ここでそれらのユースケースについて簡単に説明します。
Promise.all
並列に複数の promise を実行し、すべてが準備できるまで待ちたいとしましょう。
例えば、平行で複数の URL をダウンロードし、すべてが完了したらコンテンツを処理する場合です。
これが Promise.all
の目的です。
構文は次の通りです:
let promise = Promise.all(iterable);
Promise.all
は iterable(反復可能, 通常は promise の配列)を取り、新しい promise を返します。
新しい promise は、配列の各 promise がすべて解決され、それらの結果を配列に持つと解決(resolve)されます。
例えば、下の Promise.all
は 3 秒後に解決され、その後結果は配列 [1, 2, 3]
です。:
Promise.all([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 3000)), // 1
new Promise((resolve, reject) => setTimeout(() => resolve(2), 2000)), // 2
new Promise((resolve, reject) => setTimeout(() => resolve(3), 1000)) // 3
]).then(alert); // 1,2,3 promise が準備できた時: 各 promise は配列の中身に寄与します
結果の配列要素の順番は元の promise の順番と同じであることに注意してください。たとえ1つ目の promise が解決まで最も時間がかかったとしても、結果の配列の中では先頭にあります。
一般的なやり方は、処理データの配列を promise の配列にマップし、それを Promise.all
にラップすることです。
例えば、URL の配列がある場合、次のようにしてすべてをフェッチできます:
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://api.github.com/users/jeresig'
];
// 各 url を promise の fetch(github url) へマップする
let requests = urls.map(url => fetch(url));
// Promise.all はすべてのジョブが解決されるまで待ちます
Promise.all(requests)
.then(responses => responses.forEach(
response => alert(`${response.url}: ${response.status}`)
));
名前からGithubユーザのユーザ情報を取得するより大きな例です(id で商品の配列をフェッチできますが、ロジックは同じです):
let names = ['iliakan', 'remy', 'jeresig'];
let requests = names.map(name => fetch(`https://api.github.com/users/${name}`));
Promise.all(requests)
.then(responses => {
// すべてのレスポンスが用意できたら HTTP ステータスコードが見られます
for(let response of responses) {
alert(`${response.url}: ${response.status}`); // 各 url で 200 が表示されます
}
return responses;
})
// それぞれの中身を読むために、レスポンスの配列を response.json() の配列にマッピングします
.then(responses => Promise.all(responses.map(r => r.json())))
// すべての JSON応答が解析され、"user" はそれらの配列です。
.then(users => users.forEach(user => alert(user.name)));
いずれかの promise が reject された場合、Promise.all
により返却された promise は即座にエラーと一緒に reject します。
例:
Promise.all([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).catch(alert); // Error: Whoops!
ここでは、2つ目の promise が2秒後に reject されます。それは即座に Promise.all
の reject に繋がるため、catch
が実行されます。: reject されたエラーは Promise.all
全体の結果になります。
ある promise が reject がされると、Promise.all
は即座に reject し、配列の他の promise を完全に忘れます。これらの結果は無視されます。
例えば、上の例のように複数の fetch
呼び出しがあり、1つが失敗した場合、他の promise は依然として実行中ですが、Promise.all
はこれ以上ウォッチしません。おそらく正常に解決されますが、結果は無視されます。
promise
には “キャンセル” の考え方はないので、Promise.all
はキャンセルしません。別の章では、これに役立つ AbortController
について説明しています。がこれは Promise API の一部ではありません。
Promise.all(iterable)
は iterable
の中で非 promise の項目を許可します通常、Promise.all(iterable)
は promise の iterable (ほとんどの場合は配列)を受け付けます。しかし、もしそれらのオブジェクトが promise ではない場合、Promise.resolve
でラップします。
例えば、ここでは結果は [1, 2, 3]
になります:
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000)
}),
2, // Promise.resolve(2) と扱われる
3 // Promise.resolve(3) と扱われる
]).then(alert); // 1, 2, 3
したがって、必要に応じて準備済みの値を Promise.all
に渡せます。
Promise.allSettled
Promise.all
はいずれかの promise が reject されると、全体として reject されます。これは “白か黒か” のケースにはよいです。続行するために すべての 成功した結果が必要な場合です:
Promise.all([
fetch('/template.html'),
fetch('/style.css'),
fetch('/data.json')
]).then(render); // render メソッドはすべての fetch の結果が必要
Promise.allSettled
は結果に関わらずすべての promise が解決するまで待ちます。結果の配列は以下を持ちます:
- 成功したレスポンスの場合:
{status:"fulfilled", value:result}
- エラーの場合:
{status:"rejected", reason:error}
例えば、複数のユーザに対する情報をフェッチしたいとします。たとえ1リクエストが失敗しても、他の結果はほしいです。
Promise.allSettled
を使いましょう:
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://no-such-url'
];
Promise.allSettled(urls.map(url => fetch(url)))
.then(results => { // (*)
results.forEach((result, num) => {
if (result.status == "fulfilled") {
alert(`${urls[num]}: ${result.value.status}`);
}
if (result.status == "rejected") {
alert(`${urls[num]}: ${result.reason}`);
}
});
});
上の行 (*)
の results
は以下になります:
[
{status: 'fulfilled', value: ...レスポンス...},
{status: 'fulfilled', value: ...レスポンス...},
{status: 'rejected', reason: ...エラーオブジェクト...}
]
そのため、各 promise に対してステータスと 値 or エラー
を取得します。
Polyfill
ブラウザでは、Promise.allSettled
をサポートしていません。が polyfill するのは簡単です:
if (!Promise.allSettled) {
const rejectHandler = reason => ({ status: 'rejected', reason });
const resolveHandler = value => ({ status: 'fulfilled', value });
Promise.allSettled = function (promises) {
const convertedPromises = promises.map(p => Promise.resolve(p).then(resolveHandler, rejectHandler));
return Promise.all(convertedPromises);
};
}
このコードで、promises.map
は入力値を取り、p => Promise.resolve(p)
でそれらを promise に変換(非 promise が渡された場合に備えて)し、.then
ハンドラに追加します。
ハンドラは成功した結果 value
を {status:'fulfilled', value}
に、エラー reason
は {status:'rejected', reason}
に変換します。これは Promise.allSettled
のフォーマットです。
これで、指定された すべて の promise の結果を得る(たとえいくつかが reject されても) Promise.allSettled
が利用できます。
Promise.race
これは Promise.all
と同様ですが、すべてが完了するのを待つのではなく、最初の結果(またはエラー)のみを待ちます。
構文です:
let promise = Promise.race(iterable);
例えば、ここでは結果は 1
になります:
Promise.race([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert); // 1
なので、最初の結果/エラーが Promise.race
全体の結果になります。最初の確定した promise が “レースに勝った” 後、それ以外の結果/エラーは無視されます。
Promise.any
Promise.race
と同様ですが、最初の履行した(fulfilled) promise のみを待ち、その結果を得ます。指定された promise がすべて reject された場合、返却された promise は AggregateError
で reject されます(errors
プロパティにすべての promise エラーを格納する特別なエラーオブジェクトです)。
構文は次の通りです:
let promise = Promise.any(iterable);
例えば、ここでは結果は 1
になります:
Promise.any([
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 1000)),
new Promise((resolve, reject) => setTimeout(() => resolve(1), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert); // 1
最初の promise が一番はやいですが、reject されたため、2つ目の promise が結果になっています。最初の履行した(fulfilled)promise が “レースに勝ち”、他の結果はすべて無視されます。
これは promise がすべて失敗した例です:
Promise.any([
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Ouch!")), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Error!")), 2000))
]).catch(error => {
console.log(error.constructor.name); // AggregateError
console.log(error.errors[0]); // Error: Ouch!
console.log(error.errors[1]); // Error: Error!
});
ご覧の通り、失敗した promise のエラーオブジェクトは AggregateError
オブジェクトの errors
プロパティで利用可能です。
Promise.resolve/reject
最近のコードでは、Promise.resolve
と Promise.reject
メソッドはめったに必要とされません。async/await
構文(少し後で説明します) によって、これらがやや時代遅れになるためです。
ここでは完全性のためと、何らかの理由で async/await
が使用できない場合のために説明します。
Promise.resolve
Promise.resolve(value)
は結果 value
をもつ解決された promise を作成します。
以下と同様です:
let promise = new Promise(resolve => resolve(value));
このメソッドは、関数が promise を返すことが期待される場合の互換性のために使用されます。
例えば、以下の loadCached
関数は、URL をフェッチし、コンテンツを記憶(キャッシュ)します。同じURLに対する将来の呼び出し時には、キャッシュから即座にコンテンツが返されますが、promise にするために Promise.resolve
を使用します。これで戻り値は常に promise になります:
let cache = new Map();
function loadCached(url) {
if (cache.has(url)) {
return Promise.resolve(cache.get(url)); // (*)
}
return fetch(url)
.then(response => response.text())
.then(text => {
cache.set(url,text);
return text;
});
}
関数は promise を返すことが保証されているため、loadCached(url).then(…)
と書くことができます。loadCached
の後に常に .then
が使用でき、これが行 (*)
の Promise.resolve
の目的です。
Promise.reject
Promise.reject(error)
は error
を持つ reject された promise を作成します。
以下と同じです:
let promise = new Promise((resolve, reject) => reject(error));
実際、このメソッドはほとんど使われません。
サマリ
Promise
クラスには 6 つの静的なメソッドがあります。:
Promise.all(promises)
– すべての promise が解決するのを待って、結果の配列を返します。与えられた promise のいずれかが拒否されると、Promise.all
のエラーとなり、他のすべての結果は無視されます。Promise.allSettled(promises)
(最近追加されたメソッド) – すべての promise が完了するのを待って、以下のオブジェクト配列として結果を返します:status
:"fulfilled"
or"rejected"
value
(fulfilled の場合) orreason
(rejected の場合).
Promise.race(promises)
– 最初の promise の解決を待ち、その結果/エラーを返します。Promise.any(promises)
(最近追加されたメソッド) – 最初に履行された(fulfilled) promise を待ち、その結果を返します。指定したすべての promise が reject された場合、AggregateError
がPromise.any
のエラーになります。Promise.resolve(value)
– 与えられた値で promise を解決(resolve)しますPromise.reject(error)
– 与えられたエラーで promise を拒否(reject)します
これらのうち、Promise.all
が実践では最も一般的です。