node.jsは非同期で処理を進めることで有名ですが、ちょっとややこしいとこまで、非同期で処理してしまい、ちょっとハマったのでメモ。
やりたいこと
配列の中身を使用した文を作成して、ファイルに書きこむ
まあ単純に考えるとこんな感じで実装します。
var fs = require('fs');
var a = [1,2,3,4,5,6,7,8,9,10];
var sent = '';
a.forEach(function(num){
sent += 'hello world: ' + num + '\n';
});
fs.writeFile('hello.txt', sent, function(err){
if(err) throw err;
});
実際にhello.txtの中身は
hello world: 1
hello world: 2
hello world: 3
(以下、省略)
でした。
うん、いい感じですね。
しかし、forEachの中身の処理に時間がかかっちゃう時、
(今回は擬似的にsetTimeout()を使用して以下のように実装)
var a = [1,2,3,4,5,6,7,8,9,10];
var sent = "";
a.forEach(function(i){
setTimeout(function() {
sent += 'hello world: ' + i + '¥n';
}, 5000);
});
fs.writeFile('hello.txt', sent, function(err){
if(err) throw err;
});
この結果はなんとhello.txtは空っぽなんですねー。
非同期処理の影響でファイル書き込みが先に行われるみたいです。
それでは困る!!
この問題はasyncというモジュールを使えば解消されます。
asyncは非同期処理になってしまうNode.jsの特性を例外的に同期処理するために使われるモジュールです。
このモジュールのeachという機能を使えば処理1が終わった後に、処理2を実行するということがコールバックを駆使して指定ができます。
こんな感じ
var fs = require('fs');
var async = require('async');
var a = [1,2,3,4,5,6,7,8,9,10];
var sent = "";
async.each(a, function(i, callback){
// 処理1
setTimeout(function() {
sent += 'hello world: ' + i + '\n';
callback();
}, 5000);
}, function(err){
//処理2
if(err) throw err;
fs.writeFile('hello.txt', sent, function(err){
if(err) throw err;
});
});
見事hello.txtにも正常に出力されました。
Node.jsにおいてforEachに限らずループは想定外の状態になる恐れがあるので、僕はasync.eachをオススメします。
2013/05/17 追記
kaminaly さんよりご指摘がありまして、非同期処理になるのはsetTimeout
を使っているからだとご指摘ありました。
実際にsetTimeout
の代わりにforを大量にまわすだけの処理に変えたところ、問題なくファイル出力されました。
非同期になるきっかけは「処理に時間がかかるもの」ではなく、非同期メソッドを使うか否かのようです。どういうのが非同期メソッドなのかは後日調べます。
なので、ループを使う時は非同期メソッドに気をつけよう!ということです。