【初心者用】ちょっとだけPromiseに対しての理解を深めるための記事
Promiseにちょっとだけ詳しくなろう
非同期処理にも慣れてきたぞ!という方のPromiseに対する理解をすこしだけアップデートさせる記事にしました。
各メソッドの違いや使用どころはシンプルに紹介していますので、理解した上で各々のプロジェクトで活かしてもらいたいです。
Promise.all()
とfetchSequentially()
Promise.all 🔥
こちらはほとんどの方が知っていると思いますが、後のメソッドと比較するためにあえて記載します。Promise.all
は複数の非同期処理を並列で実行し、すべての処理が完了するのを待ってから結果を返します。
const urls = [
'https://api.example.com/data1',
'https://api.example.com/data2',
'https://api.example.com/data3'
];
Promise.all(urls.map(url => fetch(url).catch(err => err)))
.then(results => {
console.log(results); // 成功した処理の結果が全て入る
})
.catch(error => {
console.error(error); // 失敗した処理のエラーが入る
});
利点
- 並列で処理できるので実行時間が短縮される可能性がある
欠点
- 処理の実行順序は保証されない
fetchSequentially() ⛓
fetchSequentiallyは「順次」って意味なので、それを考えると処理の内容はそのまんまになってます。つまりPromise.all()の並列処理に対してfetchSequentially()
は複数の非同期処理を直列実行する関数です。1つの非同期処理が完了してから次の処理に移ります。
const urls = [
'https://api.example.com/data1',
'https://api.example.com/data2',
'https://api.example.com/data3'
];
function fetchSequentially(urls) {
const results = [];
const fetching = urls.reduce((promise, url) => {
return promise.then(() =>
fetch(url).then(res => results.push(res)).catch(err => results.push(err))
)
}, Promise.resolve());
return fetching.then(() => results);
}
-
reduce
メソッドで非同期処理を連鎖させ、前の処理が完了してから次の処理に移ります -
reduce
の初期値としてPromise.resolve()
を使用してます。これで、最初のPromiseがすぐに実行されて最初のURLのフェッチがトリガーされます。
利点
- 処理の実行順序が保証される
- 1つの処理の完了を待ってから次に進むので、並列処理に比べてリソース消費が少ない
欠点
- 処理全体の実行時間が長くなる可能性がある
まとめ
fetchSequentially()
は処理の順序を守りたい場合や、リソース消費を気にする場合に適しています。一方Promise.all
は高速な処理が求められる場合に適しています。
また、ここまで紹介したPromise.all()とfetchSequentially()は次に紹介するPromise.allSettled()
の違いを分かりやすくするためにcatch文でエラーハンドリングしてます。
Promise.all()やfetchSequentially()は配列内のどれか一つでもPromiseがrejectされるとすぐにエラーになるので、catchブロックがない場合はエラーが発生した時点で処理が停止します。
これを踏まえて次のPromise.allSettled()
をみていきましょう。
Promise.allSettled() 🕵
Promise.allSettled()は、複数のPromiseがすべて完了するのを待ってから、その結果を配列で返します。そして、その結果はPromiseが解決された場合も拒否された場合も含まれます。 つまり1つ失敗してもすべての結果を取得できるということです。catch文を書く必要がなく、Promiseの状態(fulfilled/rejected)を区別してくれます。
const fetchData1 = fetch('/data1.json');
const fetchData2 = fetch('/data2.json');
const fetchData3 = fetch('/data3.json');
Promise.allSettled([fetchData1, fetchData2, fetchData3])
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('Success:', result.value);
} else {
console.log('Error:', result.reason);
}
});
});
コード自体は簡単で、3つの異なるAPIリクエストを送信し、すべての結果を待ってから、成功した場合はデータを、失敗した場合はエラーメッセージを出力してます。
Promise.race() 🏇
Promise.race()は、読んで字の如くレースです。
複数のPromiseのうち最初に完了したPromiseの結果を成功、失敗関係なく返します。
そして、他のPromiseは無視されます。
import { timeout } from 'promise-timeout-utilities';
const fetchDataFromServer1 = fetch('/data.json');
const fetchDataFromServer2 = fetch('https://backupserver.com/data.json');
Promise.race([
fetchData1,
fetchData2,
timeout(5000, 'Request timed out')
]).then(response => console.log(response)).catch(error => console.error(error));
こちらもコード自体は簡単で、最初に完了したPromiseの結果が使われて、それ以外のPromiseは無視されるようになってます。
はしょったまとめ
Promise.all()・・・複数の非同期並列処理。catch文を書かないと処理失敗したら止まる
fetchSequentially()・・・複数の非同期直列処理。catch文を書かないと処理失敗したら止まる
Promise.allSettled()・・・複数の非同期並列処理。catch文を書かなくても成功か失敗か仕分けして返してやるよ!
Promise.race()・・・複数の非同期並列処理。成功、失敗なんて関係ねぇ!一番早く終わった結果を返すぞ!
Discussion