📖

【初心者用】ちょっとだけPromiseに対しての理解を深めるための記事

2024/05/07に公開

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