2. Node.jsの非同期処理

同期処理と非同期処理の違いを理解し、Node.jsの非同期処理の仕組みを学びます。I/Oとブロッキングの概念、同期メソッドと非同期メソッドの使い分けを実践的に学習します。

この章で学べること

  • 同期処理と非同期処理の違いを理解する
  • I/Oとブロッキングの概念を理解する
  • 同期メソッドと非同期メソッドの使い分けを学ぶ
  • 実践的な演習問題で理解を深める

同期処理と非同期処理とは?

プログラミングの処理方法には、同期処理非同期処理があります。

同期処理(Synchronous)

同期処理には次の特徴があります。

  • 処理を 1つずつ順番に実行
  • 1つの処理が終わるまで、次に進めない
console.log("A");
console.log("B");
console.log("C");
// 出力:A → B → C

同期処理は上から順番に実行されます。実行順序が変わることはありません。

非同期処理(Asynchronous)

非同期処理には次の特徴があります。

  • 時間のかかる処理(例:ファイル読み込み・通信)の結果を待たずに次の処理を進める
  • 結果が出たタイミングで値は返される
console.log("A");

// setTimeoutは非同期処理
setTimeout(() => {
  console.log("B");
}, 1000);

console.log("C");
// 出力:A → C → (1秒後に) B

C、Java、PHP、Go、Ruby、Python はすべてデフォルトで同期処理です。JavaScript/Node.jsも基本は同期処理ですが、I/O処理に関する部分は非同期処理をデフォルトとして採用しています。

I/Oとブロッキング

次に非同期処理の理解に必要なI/Oとブロッキングについて説明します。

I/O

「I/O(インプット/アウトプット)」とは、システムのディスクおよびネットワークとのやり取りのことです。具体的にはファイルの読み書き、データの送受信のことを指します。

ブロッキング

ブロッキングとは、1つの処理が完了するまで次の処理に進むことを「ブロック(阻止)」することです。

同期処理はこのブロッキングが発生します。

const name = "太郎"; // ブロッキング発生
console.log(name + "さん、こんにちは!"); // ブロッキング発生

// 出力:太郎さん、こんにちは!

一見、効率が悪いように思えますが、処理の流れが分かりやすいというメリットがあります。例えば、const name = "太郎";でブロッキングが発生しないと、次のコードで正しい名前が取得できません。瞬時に完了する処理の場合、ブロッキングは問題がありません。

ブロッキングが問題になるのは、ファイルの読み込みやデータベースへの接続など、時間のかかる処理の場合です。Node.jsはシングルスレッドで動作するため、1つの処理がブロックされると、他のすべての処理が待機状態になります。

例えば、大きなファイルを読み込むために、10秒ブロッキングが発生すると、他のリクエストは処理されず、その10秒間はサーバーが停止することになります。

具体例

  • ファイル読み込みで10秒ブロッキングが発生する → 10秒間他のリクエストは処理されず、サーバーが停止する
  • データベース処理で10秒ブロッキングが発生する → 10秒間他のリクエストは処理されず、サーバーが停止する
  • ネットワークの応答待ちで10秒ブロッキングが発生する → 10秒間他のリクエストは処理されず、サーバーが停止する

Node.jsでは、このようなブロッキングを避けるために、非同期処理(ノンブロッキング)を推奨しています。非同期処理では、このブロッキングが発生しません。

同期メソッド/非同期メソッド

同期メソッドは同期的に実行され、非同期メソッドは非同期的に実行されます。ファイルの読み込みを例にとると次のようになります。

同期処理

同期処理は、ブロッキングが発生します。readFileSyncは同期メソッドです。ファイルの読み取りが完了するまで後続の処理は実行されません。

const fs = require('node:fs');
const data = fs.readFileSync('/file.md'); // ブロッキングが発生

非同期処理

readFileは非同期メソッドです。ファイルの読み取りが完了するのを待たず、後続の処理が実行されます。

const fs = require('node:fs');

fs.readFile('/file.md', (err, data) => {
  if (err) {
    throw err;
  }
});

上記のコードを見比べると、同期処理の方が簡単に見えますが、ファイル全体が読み込まれるまで後続のコードの実行がブロックされるという欠点があります。同期メソッドには明示的に Syncとついていることが多いため、名前で識別することができます。

演習課題

演習①:順番を予想しよう

以下のコードを実行したときの出力順序を予想してください。

console.log("1. 開始");

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

console.log("3. 即座に実行");

setTimeout(() => {
  console.log("4. 0秒後");
}, 0);

console.log("5. 終了");

出力順序を選択してください

演習②:同期処理と非同期処理の識別をしよう

以下のコードの中で、同期処理と非同期処理を識別してください。

問題1
const result = 1 + 2 + 3;
問題2
const fs = require('fs');
const data = fs.readFileSync('file.txt');
問題3
fs.readFile('file.txt', (err, data) => {
  console.log(data);
});
問題4
setTimeout(() => {
  console.log('タイマー実行');
}, 1000);
問題5
const connection = database.connectSync();
問題6
database.connect((err, conn) => {
  console.log('接続完了');
});

次回予告

次回はNode.jsのコールバックについて学びます。