Node.js非同期処理

2025年8月24日

Node.jsはシングルスレッドでどのようにして並行処理をしているか?

並行処理のイメージ例としては「コンビニのレジでお弁当をレンジ温める」間に「お会計をする」です

Node.jsは並行処理もマルチスレッドではなく、シングルスレッドで実現しています

マルチスレッド

  • プロセス … 実行中のアプリケーション(Node.jsなら「node.exe」など)
  • スレッド … プロセスの中で実際に処理を行う単位。
    1つのプロセスの中に複数のスレッドを持つことができます。

Node.jsはイベントループ

イベントループと非同期処理で実現しています

同期処理、非同期処理のちがい

「同期処理」はコードを上から順番に実行し、一つの処理が終わるまで次に進まない方式。

処理が完了するまで他の作業は待機状態(ブラウザでは何もできないといった感じ)になる。

「非同期処理」はバックグラウンドで実行し、メインの処理を止めずに続行する方式。処理完了時にコールバック関数で結果を受け取る。

非同期処理の結果を受け取って実行されるコールバック関数の処理は同期処理です

イベントループとは

一言でいうと「コールスタックが空になったらタスクキューから関数を移動させる監視プログラム」です

タスクキュー

実行待ちのコールバック関数を順番に保存するデータ構造をタスクキューといいます

setTimeout(() => console.log('完了'), 1000);

上記であれば1秒後にタスクキューに追加されます

タスクキューで待機し、コールスタック(メイン処理の場所)が空になったら順番に実行

(すべての処理は最終的にコールスタックで実行されます)

つまり

非同期処理がバックグラントで走る
↓
非同期処理が完了
↓
コールバック関数の処理がタスクキューに追加
↓
同期処理がすべて終了し、コールスタックが空になる
↓
コールバック関数の処理が実行される

JavaScriptの非同期処理は段階的に改善されてきました

コールバック関数

非同期処理の実装方法でコールバック関数の利用は最も基本的なパターンです

誤解しないように整理すると、コールバック関数=非同期処理ではないです。

コールバック関数は非同期処理の関数の引数です。

非同期処理を行う関数を非同期APIといいます、ブラウザやNode.jsが提供しています

setTimeout(test, 3000);非同期処理(非同期API)
function test() {
console.log(“test”);
}
コールバック関数(setTimeoutの引数)

非同期APIでよく使われるもの

  • setTimeout
  • DOMイベント(addEventListener
  • XMLHttpRequest(古典的なAjax通信)
  • fs.readFile(Node.js のファイル読み込み)

これらは 仕様として「非同期で動作する」 と決められています。

setTimeoutのシンプルな例

setTimeoutは第一引数に実行したい関数、第二引数に実行するタイミング

▼書き方

setTimeout(test, 3000);
function test() {
    console.log("test");
}

// 無名関数
setTimeout(function () {
console.log("test");    
}, 3000);

// アロー関数
setTimeout(() => {
    console.log("test");
}, 3000);
console.log("処理1: スタート");

// 非同期処理(2秒後に実行)
setTimeout(function () {
  console.log("処理2: これは2秒後に実行されました(コールバック)");
}, 2000);

console.log("処理3: すぐ実行");

▼コンソール

処理1: スタート
処理3: すぐ実行
処理2: これは2秒後に実行されました(コールバック)
処理1同期処理
同期処理で即実行
setTimeout非同期処理
非同期処理で「2秒待つ」がバックグラウンドで走る
※終了後にコールバック(処理2)が実行待ちに入る
処理3同期処理
②の終了を待たずに即実行
処理2同期処理
②が終了して、タスクキューに「処理2」が追加されかつ、
同期処理がすべて終了しているので、コールスタックに移動して実行される

コールバック地獄

setTimeout(() => {
  console.log("1秒後");
  setTimeout(() => {
    console.log("2秒後");
    setTimeout(() => {
      console.log("3秒後");
      setTimeout(() => {
        console.log("4秒後");
      }, 1000);
    }, 1000);
  }, 1000);
}, 1000);

Promise

Promiseとは、JavaScriptで非同期処理(時間のかかる処理)の結果を扱うオブジェクトです。

// Promiseオブジェクトを定義
function getData() {
  return new Promise(function executor(resolve, reject) { // 実行例(わかりやすく名前付き関数で記述)
    setTimeout(function() {
      if (Math.random() > 0.5) {
        // 成功した値を「登録」する
        // この値は then メソッドに渡されたコールバック関数の引数 result に渡る(resultでなくて好きな名前でOK)
        resolve("成功した値として「データ取得成功」を登録、これは then メソッドのコールバック関数に result として渡ります");
      } else {
        // 失敗理由を「登録」する
        // この値は catch メソッドに渡されたコールバック関数の引数 error に渡る(errorでなくて好きな名前でOK)
        reject("失敗: エラーが発生しました(この文字列が catch のコールバック関数に error として渡ります)");
      }
    }, 1000);
  });
}

// 実行例(わかりやすく名前付き関数で記述)
getData()
 .then(function onFulfilled(result) {
   // resolve で登録した成功の値が result に入る
   console.log("成功:", result);
 })
 .catch(function onRejected(error) {
   // reject で登録した失敗理由が error に入る
   console.error("失敗:", error);
 })
 .finally(function onFinally() {
   // 成功でも失敗でも最後に必ず実行される
   console.log("完了");
 });

非同期処理なのは「then/catch/finally に登録された関数」

コード部分処理の種類
getData()同期
getData 内の setTimeout非同期
thenメソッド同期
onFulfilled、onRejected、onFinally非同期
console.log(result)(非同期関数内)同期

resolvereject とは?

resolve(“成功: データを取得しました”) の “成功: データを取得しました” は引数です
この引数は「成功時の値」として Promise に渡されます。

その値が then(onFulfilled) の onFulfilled の引数に入ります。

  • resolve(value)
    → 成功したときに呼ぶ関数。
    → Promiseの状態を「fulfilled(成功)」にして、value.then(...) に渡す。
  • reject(error)
    → 失敗したときに呼ぶ関数。
    → Promiseの状態を「rejected(失敗)」にして、error.catch(...) に渡す。
  • .then().catch()でチェーンできる
  • コールバック地獄を解決

async/await

  • 同期的なコードのように書ける

先ほどの非同期処理をasync/awaitを使用して書くと

// Promiseオブジェクトを定義(この部分は変更なし)
function getData() {
  return new Promise(function executor(resolve, reject) { // 実行例(わかりやすく名前付き関数で記述)
    setTimeout(function() {
      if (Math.random() > 0.5) {
        // 成功した値を「登録」する
        // この値は await で受け取られる
        resolve("成功した値として「データ取得成功」を登録、これは await で受け取られます");
      } else {
        // 失敗理由を「登録」する
        // この値は try-catch の catch ブロックで error として受け取られる
        reject("失敗: エラーが発生しました(この文字列が catch ブロックで error として受け取られます)");
      }
    }, 1000);
  });
}

// async/await版の実行例
async function executeGetData() {
  try {
    // await で Promise の結果を待つ
    // resolve された値が result に入る
    const result = await getData();
    console.log("成功:", result);
  } catch (error) {
    // reject された値が error に入る
    console.error("失敗:", error);
  } finally {
    // 成功でも失敗でも最後に必ず実行される
    console.log("完了");
  }
}

// 関数を実行
executeGetData();

awaitはasync関数の中でのみ使用できます asyncなしでawaitを使うとシンタックスエラーになります