🌊

【JavaScript】非同期処理をPromiseで制御する

2024/01/23に公開

そもそも同期処理って?

コードが上から書かれた順序に従って、実行される処理。
1つのコードの処理が完了しないと次の行のコードには進めない。

非同期処理とは

特定のコード(例えばHTTPリクエスト、データベース操作など)が背後で実行されている間にも、プログラムが次の行の行のコードの実行を続けられる処理のこと。
非同期処理

  • HTTPリクエスト fetchなど
  • データベース操作
  • ファイル読み書き
  • setTimeout関数 などなど

非同期処理の結果を扱いやすくするための便利ツール
非同期処理の実行・完了を制御できる

3つのログを表示するコード

const displayMessage = () => {
  setTimeout(() => {
    console.log(`disPlayMessage関数が実行`);
  }, 2000);
};

console.log("関数を実行します。");
displayMessage();
console.log("関数が実行されました。");

出力結果

関数を実行します。
関数が実行されました。
displayMessage関数が実行

setTImeout関数は非同期処理なので、displayMessage関数が実行というログは、同期処理の最後のconsole.log("関数が実行されました。)による出力が終わった後に表示される。

displayMessage関数が実行というメッセージ表示の完了を待ってから、関数が実行されました。を表示させたい場合、非同期処理の結果を扱いやすくするPromiseAsync/Awaitなどを用いると制御が可能になる。

Promiseとは

  • JavaScriptnに標準で備わっているビルトインオブジェクト
  • 非同期処理が完了し、その結果が成功か失敗かに応じて適切な処理を行うことができる
  • 3種類の状態を持つ
    • pending(待機):非同期処理が終わっていない状態
    • fulfilled(成功):非同期処理が正常に完了した状態
    • rejected(拒否):非同期処理が失敗した状態
const displayMessage = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(`displayMessage関数が実行`);
      resolve();  // Promiseが解決される
    }, 2000);
  });
};

console.log("関数を実行します。");
displayMessage().then(() => {
  console.log("関数が実行されました。");
});

出力結果

関数を実行します。
displayMessage関数が実行
関数が実行されました。

return new Promise((resolve, reject) => {処理});
Promiseの引数に関数を設定し、その関数の第1引数にはresolve、第2引数にはrejectを設定する。(慣例的にresolveとrejectと書くらしい)

重要なのは第1引数が成功時の処理、第2引数が失敗時の処理を担う。第2引数は省略可能。

resolve()が実行
非同期処理が正常に完了したことを表し、Promiseの状態はfulfilledに変わる。
reject()が実行
非同期処理が失敗したことを表し、Promiseの状態はrejectedに変わる。

もっと細かく(自分なりの理解)

PromiseはJavaScriptに備わっているビルトインオブジェクト。(クラス、設計図のような理解)
new Promise(...)でPromiseをインスタンス化して実体をつくる。

インスタンス化されたPromiseオブジェクトはthencatchメソッドを持っており、Promiseオブジェクトの状態によって、これらを使い分けできるようになる!
作成時点ではPromiseの状態はpendingのまま。

return new Promise(...)で作成したインスタンスを呼び出し元の関数displayMesage()に返している。
そうすると、呼び出し元の関数displayMessage()が状態によってthenまたはcatchメソッドを実行する。

関数の中でresolve()が実行されるとPromiseオブジェクトはfulfilled状態に変化し、thenの処理が実行される。
reject()が実行されるか、エラーが発生すると(throw new Error()を実行)、Promiseオブジェクトは``rejected状態に変化し。catchの処理が実行される。

追記(静的メソッドを使う場合)

Promiseは静的メソッドのresolverejectを持っているため、インスタンス化しなくてもPromise.resolve(value)Promise.reject(value)与えられたvalueで解決することができる!

使い分け

new Promise

以下のような非同期処理を行い、非同期の処理が完了した後resolverejectを呼び出したいとき

  • setTimeout
  • fetch
  • データベース操作
  • ファイル読み書き,など

Promiseの静的メソッド

  • 非同期処理を行わないときで、決まった値でresolverejectして、チェーンメソッドを使いたい場合
  • Promise.resolve:特定の値ですぐにPromisefulfillしたいとき
  • Promise.reject:発生したエラーですぐにPromiserejectしたいとき

コード

new Promiseで非同期処理を制御する場合

new Promise
const func = () => {
  return new Promise((resolve, reject) => {
    // 非同期処理を実行
    setTimeout(() => {
      const result = Math.random();
      if (result > 0.5) {
        resolve('成功');
      } else {
        reject(new Error('失敗'));
      }
    }, 1000);
  });
};

func()
  .then((value) => {
    console.log(value);
  })
  .catch((error) => {
    console.error(error);
  });
  
  
// fetchするとき
fetch('https://example.com/data')
  .then(response => {
    if (!response.ok) {
      throw new Error('ネットワーク応答が正常ではありません');
    }
    return response.json();
  })
  .then(data => {
    console.log('取得したデータ:', data);
  })
  .catch(error => {
    console.error('エラーが発生しました:', error);
  });

fetchsetTimeoutと違ってPromiseを返す関数なので、new Promiseでラップしなくてもよい、みたい!取得したresponseをjsonに変換したデータをreturnするだけで、

Promise.メソッドで即座にPromiseを解決(拒否)する場合

// resolve
Promise.resolve(value)
  .then(data => {
    console.log(data);  //
  });

// reject
Promise.reject(new Error('エラーが発生'))
  .catch(error => {
    console.error(error);  // エラーメッセージが出力される
  });

Discussion