Nodejs での多くのコールバック再帰の管理
-
05-07-2019 - |
質問
Nodejs では、事実上、ブロッキング I/O 操作はありません。これは、ほぼすべての Nodejs IO コードに多くのコールバックが含まれることを意味します。これは、データベース、ファイル、プロセスなどへの読み取りおよび書き込みに適用されます。この典型的な例は次のとおりです。
var useFile = function(filename,callback){
posix.stat(filename).addCallback(function (stats) {
posix.open(filename, process.O_RDONLY, 0666).addCallback(function (fd) {
posix.read(fd, stats.size, 0).addCallback(function(contents){
callback(contents);
});
});
});
};
...
useFile("test.data",function(data){
// use data..
});
次のようなコードを書くことを期待しています 多くの IO 操作なので、書くことになると思います 多くの コールバック。私はコールバックの使用に非常に慣れていますが、すべての再帰が心配です。再帰が多すぎて、どこかでスタックがパンクしてしまう危険はありますか?何千ものコールバックを使用してキーと値のストアに何千もの個別の書き込みを行った場合、プログラムは最終的にクラッシュしますか?
私はその影響を誤解または過小評価していませんか?そうでない場合、Nodejs のコールバック コーディング スタイルを使用しながらこれを回避する方法はありますか?
解決
あなたが示すコードはどれも再帰を使用していません。電話をかけるとき useFile
それは呼びます posix.stat()
, 、それは戻ります、そして useFile
完了まで実行されたため終了します。しばらくしてから電話がかかってきたとき、 posix.stat()
完了しました 基礎となるシステム内で 結果が利用可能になると、そのために追加したコールバック関数が実行されます。それは電話します posix.open()
, 、最後まで実行されると終了します。ファイルが正常に開かれると、次のコールバック関数が実行されます。 それ 呼び出して実行します posix.read()
, 、そして、それも完了するまで実行されたため、終了します。最後に、読み取りの結果が利用可能になると、最も内側の関数が実行されます。
重要な点は、各関数が、 posix.*()
関数はノンブロッキングです。つまり、それらはすぐに戻り、基礎となるシステムで何らかの魔法が開始されることになります。したがって、各関数が終了し、その後のイベントによって次の関数が実行されます。しかし、どの時点でも再帰はありません。
コードの入れ子構造により、外部のコードが独自のエンドポイントに到達する前に、内部のコードが終了する必要があるような印象を与える場合があります。しかし、このスタイルの非同期イベント駆動型プログラミングでは、ネストを次の観点から見る方が合理的です。 より深い => より後で起こる.
編集:入れ子になった各関数の終了直前にログ ステートメントをいくつか追加してみてください。これは、完了する順序が外側から内側に向かって行われることを示すのに役立ちます。
他のヒント
デバッグ出力が追加された同じ例(出力については以下を参照):
usefile.js:
var sys = require("sys"),
posix = require("posix");
var useFile = function(filename,callback){
posix.stat(filename).addCallback(function (stats) {
posix.open(filename, process.O_RDONLY, 0666).addCallback(function (fd) {
posix.read(fd, stats.size, 0).addCallback(function(contents){
callback(contents);
sys.debug("useFile callback returned");
});
sys.debug("read returned");
});
sys.debug("open returned");
});
sys.debug("stat returned");
};
useFile("usefile.js",function(){});
出力:
DEBUG: stat returned
DEBUG: open returned
DEBUG: read returned
DEBUG: useFile callback returned
試すことができます
http://github.com/creationix/do
または私がやったようにあなた自身をロールバックします。今のところエラー処理の欠落を気にしないでください(それを無視してください);)
var sys = require('sys');
var Simplifier = exports.Simplifier = function() {}
Simplifier.prototype.execute = function(context, functions, finalFunction) {
this.functions = functions;
this.results = {};
this.finalFunction = finalFunction;
this.totalNumberOfCallbacks = 0
this.context = context;
var self = this;
functions.forEach(function(f) {
f(function() {
self.totalNumberOfCallbacks = self.totalNumberOfCallbacks + 1;
self.results[f] = Array.prototype.slice.call(arguments, 0);
if(self.totalNumberOfCallbacks >= self.functions.length) {
// Order the results by the calling order of the functions
var finalResults = [];
self.functions.forEach(function(f) {
finalResults.push(self.results[f][0]);
})
// Call the final function passing back all the collected results in the right order
finalFunction.apply(self.context, finalResults);
}
});
});
}
そしてそれを使用した簡単な例
// Execute
new simplifier.Simplifier().execute(
// Context of execution
self,
// Array of processes to execute before doing final handling
[function(callback) {
db.collection('githubusers', function(err, collection) {
collection.find({}, {limit:30}, function(err, cursor) {
cursor.toArray(function(err, users) { callback(users); })
});
});
},
function(callback) {
db.collection('githubprojects', function(err, collection) {
collection.find({}, {limit:45, sort:[['watchers', -1]]}, function(err, cursor) {
cursor.toArray(function(err, projects) { callback(projects); })
});
});
}
],
// Handle the final result
function(users, projects) {
// Do something when ready
}
);
あなたのものは大丈夫です。 Expressで再帰呼び出しを行ってHTTPリダイレクトを追跡していますが、あなたがしているのは「トラバーサル」です再帰ではない
「ステップ」もご覧ください( http://github.com/creationix/step)またはgithubの「flow-js」。これにより、コールバックフローをより自然なスタイルで記述できます。これにより、再帰が行われていないことも明確になります。
JavaScriptと同様に、Node.jsを使用して再帰呼び出しを行うことができます。再帰の深さの問題に遭遇した場合(NickFitzが指摘しているように、その危険性はないようです)、代わりにインターバルタイマーを使用するようにコードを書き換えることができます。