情報アイランド

「情報を制する者は世界を制す」をモットーに様々な情報を提供することを目指すブログです。現在はプログラミング関連情報が多めですが、投資関連情報も取り扱っていきたいです。

Node.jsで正しく非同期コールバック関数の呼び出しを行う

非同期処理を実現する方法には主に2つあります。

1つは非同期コールバック関数を使用する方法であり、もう1つは非同期イベントを使用する方法です。

それでは、正しく非同期コールバック関数の呼び出しを行うにはどのようにコードを記述すれば良いでしょうか?

状況設定

少し作為的かもしれませんが、下のような関数を実装することを例に考えましょう。

function f (flag, callback) {
}

この関数では第1引数のフラグがtrueである場合にのみtextfile.txtという名称のテキストファイルのデータを非同期的に読み込み、第2引数の非同期コールバック関数を呼び出すものとします。フラグがfalseである場合には何もしないで非同期コールバック関数を呼び出すものとします。

また、コールバック関数の第1引数はエラーオブジェクトとし、第2引数は読み込んだテキストとするものとします。ただし、テキストファイルのデータを読み込まなかった場合には第2引数はnullとするものとします。

なお、テキストファイルのデータを読み込む方法に関しては下の記事を参照してください。

悪い例

悪い例から書きます。

下のコードは非同期コールバック関数が正しく呼び出されていない例です。

var fs = require('fs');

function f (flag, callback) {
    if (flag) {
        fs.readFile('textfile.txt', 'utf-8', function (err, data) {
            if (err) {
                callback(err, null);
            }
            else {
                callback(null, data)
            }
        });
    }
    else {
        callback(null, null);
    }
}

何が悪いか分かりますか?

悪い例の実行結果

悪い例を実行し、その結果から何が悪いのかを考えましょう。

まず、下のコードを試してみましょう。

関数fの第1引数にtrueを渡した場合です。

f(true, function (err, text) {
    if (err) {
        console.error(err);
        process.exit(1);
    }
    else if (text) {
        console.log(text);
    }
    else {
        console.log('no data.');
    }
    console.log('data load end.');
});

console.log('data load start.');

現在のフォルダでasync-callback-1.jsという名称のファイルに上のコードを保存し、実行してみます。

また、現在のフォルダにはtextfile.txtという名称のテキストファイルが存在するものとします。

C:\work\node>type textfile.txt
これはテキストファイルの内容です。

実行結果は下のようになりました。

C:\work\node>node async-callback-1.js
data load start.
これはテキストファイルの内容です。
data load end.

特に問題はなさそうに見えます。

それでは、下のコードはどうでしょうか。

関数fの第1引数にfalseを渡した場合です。

f(false, function (err, text) {
    if (err) {
        console.error(err);
        process.exit(1);
    }
    else if (text) {
        console.log(text);
    }
    else {
        console.log('no data.');
    }
    console.log('data load end.');
});

console.log('data load start.');

現在のフォルダでasync-callback-2.jsという名称のファイルに上のコードを保存し、実行してみます。

実行結果は下のようになりました。

C:\work\node>node async-callback-2.js
no data.
data load end.
data load start.

おっと。

メッセージが表示される順番がおかしいですね

関数fで第1引数がfalseである場合にどのようにコードが実行されるか見てみると、確かにすぐに非同期コールバック関数が呼び出されるようになっています。

これでは処理の実行の順番が

  • 関数fの呼び出し→非同期コールバック関数内の処理→関数fの呼び出しの後の処理

となるため、メッセージが表示される順番がおかしくなるのは当然です。

非同期コールバック関数は現在実行中の処理の実行が完了してから呼び出されることが前提となっているものであるため、上の例で言えば、処理の実行の順番が

  • 関数fの呼び出し→関数fの呼び出しの後の処理→非同期コールバック関数内の処理

とならなければ非同期コールバック関数が正しく呼び出されているとは言えません。

それでは、どのようにすれば正しい順番で非同期コールバック関数を呼び出すことができるでしょうか?

良い例

良い例を書きましょう。

下のコードは非同期コールバック関数が正しく呼び出されている例です。

var fs = require('fs');

function f (flag, callback) {
    if (flag) {
        fs.readFile('textfile.txt', 'utf-8', function (err, data) {
            if (err) {
                callback(err, null);
            }
            else {
                callback(null, data)
            }
        });
    }
    else {
        process.nextTick(function () {
            callback(null, null);
        });
    }
}

悪い例と比べて何が違うでしょうか?

関数fの一番外側のelseの内容が変わっています。

悪い例では非同期コールバック関数を直接呼び出していましたが、良い例では非同期コールバック関数を呼び出す関数をprocess.nextTick関数のコールバック関数としています。

このprocess.nextTick関数が非同期コールバック関数を正しく呼び出すための鍵です

この関数により処理を次のイベントループに持ち越すことができます。

そして、そのようにすることで、上の例で言えば、処理の実行の順番が

  • 関数fの呼び出し→関数fの呼び出しの後の処理→非同期コールバック関数内の処理

という正しいもので非同期コールバック関数を呼び出すことができるようになります。

process.nextTick関数に関しては下の記事を参照してください。

良い例の実行結果

念のため良い例を実行し、実行結果を見ておきましょう。

関数fの第1引数にtrueを渡した場合です。

f(true, function (err, text) {
    if (err) {
        console.error(err);
        process.exit(1);
    }
    else if (text) {
        console.log(text);
    }
    else {
        console.log('no data.');
    }
    console.log('data load end.');
});

console.log('data load start.');

現在のフォルダでasync-callback-3.jsという名称のファイルに上のコードを保存し、実行してみます。

実行結果は下のようになりました。

C:\work\node>node async-callback-3.js
data load start.
これはテキストファイルの内容です。
data load end.

正しい結果となっています。

関数fの第1引数にfalseを渡した場合です。

f(false, function (err, text) {
    if (err) {
        console.error(err);
        process.exit(1);
    }
    else if (text) {
        console.log(text);
    }
    else {
        console.log('no data.');
    }
    console.log('data load end.');
});

console.log('data load start.');

現在のフォルダでasync-callback-4.jsという名称のファイルに上のコードを保存し、実行してみます。

実行結果は下のようになりました。

C:\work\node>node async-callback-4.js
data load start.
no data.
data load end.

正しい結果となっています。

結論

非同期コールバック関数を呼び出す場合には必要に応じてprocess.nextTick関数を使え!

関連

pizyumi
プログラミング歴19年のベテランプログラマー。業務システム全般何でも作れます。現在はWeb系の技術を勉強中。
スポンサーリンク

-Node.js