Node.js コールバック関数

Node.js は Chrome V8 エンジンをベースにした JavaScript 実行環境であり、JavaScript をブラウザから切り離してサーバー側で実行できるようにします。

Node.js の中核的な特徴の一つは、ノンブロッキング I/O(入力/出力)モデルであり、これにより Node.js は高並行処理のネットワークアプリケーションに非常に適しています。

Node.js の非同期プログラミングの直接的な現れがコールバックです。

Node.js において、コールバック関数は非同期プログラミングのパターンであり、ファイルの読み書き、データベースとの連携、ネットワークリクエストなどの I/O 操作を処理するために使用されます。これらの操作は通常長い時間がかかるため、同期方式を採用すると、操作が完了するまでプログラム全体の実行がブロックされてしまいます。

コールバック関数を使用することで、Node.js は I/O 操作の進行中も他のコードを実行し続け、I/O 操作が完了したらコールバック関数を実行します。

Node.js はシングルスレッドですが、イベント駆動とコールバックメカニズムによって非同期操作を実現しています。

使用シーン

  • ファイルの読み取り、ファイルの書き込みなどの I/O 操作。
  • ネットワークリクエストの処理。
  • データベースのクエリ。

例えば、ファイルを読み取りながら他のコマンドを実行でき、ファイルの読み取りが完了したら、ファイルの内容をコールバック関数のパラメータとして返します。これにより、コードの実行中にファイル I/O 操作のブロックや待機が発生しません。これにより Node.js のパフォーマンスが大幅に向上し、大量の並行リクエストを処理できます。

コールバック関数は通常、関数の最後のパラメータとして現れます:

function foo1(name, age, callback) { }
function foo2(value, callback1, callback2) { }

インスタンス

ブロッキングコードのインスタンス

ファイル input.txt を作成し、内容は以下の通りです:

サンプル官网地址:www.example.com

main.js ファイルを作成し、コードは以下の通りです:

インスタンス

var fs = require("fs");

var data = fs.readFileSync('input.txt');

console.log(data.toString());
console.log("プログラム実行終了!");

上記のコードの実行結果は以下の通りです:

$ node main.js
サンプル官网地址:www.example.com

程序执行结束!

ノンブロッキングコードのインスタンス

ファイル input.txt を作成し、内容は以下の通りです:

サンプル官网地址:www.example.com

main.js ファイルを作成し、コードは以下の通りです:

インスタンス

var fs = require("fs");

fs.readFile('input.txt', function (err, data) {
    if (err) return console.error(err);
    console.log(data.toString());
});

console.log("プログラム実行終了!");

パラメータの説明:

  • errはエラーオブジェクトで、読み取りに失敗した場合に値を持ちます。
  • dataは読み取られたファイルの内容です。

上記のコードの実行結果は以下の通りです:

$ node main.js
程序执行结束!
サンプル官网地址:www.example.com

上記の2つのインスタンスで、ブロッキングとノンブロッキング呼び出しの違いを理解しました:

  • 最初のインスタンスは、ファイルの読み取りが完了した後にプログラムを実行します。

  • 2つ目のインスタンスでは、ファイルの読み取り完了を待つ必要がないため、ファイル読み取り中に次のコードを同時に実行でき、プログラムのパフォーマンスが大幅に向上します。

したがって、ブロッキングは順番に実行されますが、ノンブロッキングは順番を必要としません。そのため、コールバック関数のパラメータを処理する必要がある場合は、コールバック関数内に記述する必要があります。


コールバック地獄(Callback Hell)

複数の非同期操作を順番に実行する必要がある場合、コールバック関数によってコードのネストが発生し、コードの読み取りや保守が難しくなります。

インスタンス

fs.readFile('file1.txt', 'utf8', (err, data1) => {
    if (err) {
        console.error('Error reading file1:', err);
        return;
    }

    fs.readFile('file2.txt', 'utf8', (err, data2) => {
        if (err) {
            console.error('Error reading file2:', err);
            return;
        }

        fs.readFile('file3.txt', 'utf8', (err, data3) => {
            if (err) {
                console.error('Error reading file3:', err);
                return;
            }

            console.log('Data from all files:', data1, data2, data3);
        });
    });
});

コードの可読性と保守性を向上させるために、以下の方法を使用できます:

  • Promises:Promises は新しい非同期プログラミングパターンであり、チェーン方式で非同期操作を処理できるため、コールバック地獄を回避できます。
  • async/await:Promises に基づいており、async/awaitは同期コードに近いスタイルの非同期プログラミング方式を提供し、非同期コードをより理解しやすく、保守しやすくします。
  • イベント駆動プログラミング:Node.js はイベント駆動プログラミングもサポートしており、イベントの監視とトリガーによって非同期操作を処理します。

1、async/await を使用する

async/await は ES2017 で導入されたシンタックスシュガーであり、非同期操作をより簡単に処理でき、コールバック地獄を回避できます。

インスタンス

const fs = require('fs').promises;

async function readFiles() {
    try {
        const data1 = await fs.readFile('file1.txt', 'utf8');
        const data2 = await fs.readFile('file2.txt', 'utf8');
        const data3 = await fs.readFile('file3.txt', 'utf8');

        console.log('Data from all files:', data1, data2, data3);
    } catch (err) {
        console.error('Error reading files:', err);
    }
}

readFiles();

2、Promises を使用する

Promises はもう一つの非同期操作の処理方法であり、then メソッドをチェーン呼び出しして、ネストされたコールバックを回避できます。

インスタンス

const fs = require('fs').promises;

fs.readFile('file1.txt', 'utf8')
    .then(data1 => {
        console.log('Data from file1:', data1);
        return fs.readFile('file2.txt', 'utf8');
    })
    .then(data2 => {
        console.log('Data from file2:', data2);
        return fs.readFile('file3.txt', 'utf8');
    })
    .then(data3 => {
        console.log('Data from file3:', data3);
    })
    .catch(err => {
        console.error('Error reading files:', err);
    });

コールバック関数は Node.js で非同期操作を処理する基本的な方法ですが、アプリケーションの複雑性が増すにつれて、コールバック地獄の問題はますます顕著になります。async/await や Promises を使用することで、コードの可読性と保守性を大幅に改善できます。

その他の拡張