[英]How to wrangle Node.JS async
我正在努力解决如何克服和处理Node.JS的异步特性。 我已经做了很多阅读,并尝试通过使用消息传递解决方案或回调函数使Node达到我想要的目的。
我的问题是我有一个要构造函数加载文件并填充数组的对象。 然后,我希望对该函数的所有调用都使用该加载的数据。 因此,我需要原始调用来等待文件被加载,所有后续调用都需要使用已经加载的私有成员。
我的问题是,即使加载返回数据的函数带有回调,加载数据并获取数据的函数也会异步执行。
无论如何,有什么简单的我想念的吗? 还是我可以在这里使用更简单的模式? 此函数应返回部分已加载文件,但返回未定义。 我已经检查过该文件实际上正在加载,并且可以正常工作。
function Song() {
this.verses = undefined;
this.loadVerses = function(verseNum, callback) {
if (this.verses === undefined) {
var fs = require('fs'),
filename = 'README.md';
fs.readFile(filename, 'utf8', function(err, data) {
if (err) {
console.log('error throw opening file: %s, err: %s', filename, err);
throw err;
} else {
this.verses = data;
return callback(verseNum);
}
});
} else {
return callback(verseNum);
}
}
this.getVerse = function(verseNum) {
return this.verses[verseNum + 1];
}
}
Song.prototype = {
verse: function(input) {
return this.loadVerses(input, this.getVerse);
}
}
module.exports = new Song();
这就是我从另一个模块使用歌曲模块的方式
var song = require('./song');
return song.verse(1);
“我的问题是,即使返回带有回调的函数,加载数据和获取数据的函数也会异步执行。”
@AlbertoZaccagni我的意思是说这行
return this.loadVerses(input, this.getVerse);
当我希望文件等待回调时,在文件加载之前返回。
这就是节点的工作方式,我将尝试通过一个例子来阐明它。
function readFile(path, callback) {
console.log('about to read...');
fs.readFile(path, 'utf8', function(err, data) {
callback();
});
}
console.log('start');
readFile('/path/to/the/file', function() {
console.log('...read!');
});
console.log('end');
您正在读取文件,并且在控制台中可能会有
您可以单独尝试一下以查看实际效果,并对其进行调整以了解要点。 在这里需要注意的重要一点是,您的代码将继续运行,跳过回调的执行,直到读取文件为止。
仅仅因为您声明了回调并不意味着执行将暂停,直到调用该回调然后继续执行。
所以这就是我要更改代码的方式:
function Song() {
this.verses = undefined;
this.loadVerses = function(verseNum, callback) {
if (this.verses === undefined) {
var fs = require('fs'),
filename = 'README.md';
fs.readFile(filename, 'utf8', function(err, data) {
if (err) {
console.log('error throw opening file: %s, err: %s', filename, err);
throw err;
} else {
this.verses = data;
return callback(verseNum);
}
});
} else {
return callback(verseNum);
}
}
}
Song.prototype = {
verse: function(input, callback) {
// I've removed returns here
// I think they were confusing you, feel free to add them back in
// but they are not actually returning your value, which is instead an
// argument of the callback function
this.loadVerses(input, function(verseNum) {
callback(this.verses[verseNum + 1]);
});
}
}
module.exports = new Song();
要使用它:
var song = require('./song');
song.verse(1, function(verse) {
console.log(verse);
});
我忽略了
[汇总为答案,并根据我之前的评论进行了扩展]
TL; DR您需要对代码进行结构化,以便任何操作的结果只能在该操作的回调中使用,因为您无法在其他任何地方访问它。
虽然将其分配给外部全局变量当然可以按预期工作,但是只有在回调触发后才会发生,这是在您无法预测的时间发生的。
评论
回调不返回值,因为就其本质而言,它们将在将来的某个时间执行。
一旦将回调函数传递到控制异步函数中,则在周围函数决定调用它时将执行该回调函数。 您无法控制它,因此等待返回结果将不起作用。
您的示例代码song.verse(1);
不能期望它返回任何有用的东西,因为它会被立即调用,并且由于尚未触发回调,因此将仅返回它可以的唯一值: null
。
恐怕这种对带有传递的回调的异步函数的依赖是NodeJS不可操作的功能。 它是它的核心 。
不过不要灰心。 对这里所有NodeJS问题的快速调查非常清楚地表明,这种只能在其回调中使用异步操作的结果的想法,是任何了解如何在NodeJS中编程的人的最大障碍。
有关正确构造NodeJS代码的各种方法的真正出色的解释/教程,请参阅《使用承诺,生成器和其他方法来管理Node.js回调地狱》 。
我相信它清楚,简洁地描述了您面临的问题,并提供了几种正确重构代码的方法。
此处提到的两个功能,即Promises和Generators,是编程功能/概念,我认为对您的理解很有用。
Promises
(或一些给他们打电话, Futures
)是/是一个编程抽象,允许一个线性写代码多一点的if this then that
风格,像
fs.readFileAsync(path).then(function(data){
/* do something with data here */
return result;
}).catch(function(err){
/* deal with errors from readFileAsync here */
}).then(function(result_of_last_operation){
/* do something with result_of_last_operation here */
if(there_is_a_problem) throw new Error('there is a problem');
return final_result;
})
.catch(function(err){
/* deal with errors when there_is_a_problem here */
}).done(function(final_result){
/* do something with the final result */
});
实际上,Promises只是以更线性的方式封送标准回调金字塔的一种手段。 (我个人认为他们需要一个新名字,因为“将来可能会出现的某些价值的承诺”的想法不是一个容易缠住的人,至少对我而言不是。)
承诺通过(在幕后)重组“回调地狱”来做到这一点:
asyncFunc(args,function callback(err,result){
if(err) throw err;
/* do something with the result here*/
});
变得更类似于:
var p=function(){
return new Promise(function(resolve,reject){
asyncFunc(args,function callback(err,result){
if(err) reject(err)
resolve(result);
});
});
});
p();
您提供给resolve()
任何值将成为下一个“ then-able”回调的唯一参数,并且任何错误都通过rejected()
传递,因此任何.catch(function(err){ ... })
您定义的处理程序。
Promise还可以执行(某种标准) async
模块所期望的所有操作,例如串行或并行运行回调以及对数组的元素进行操作,一旦收集了所有结果,便将其收集的结果返回给回调。
但是您会注意到Promises并没有完全按照您想要的去做,因为所有内容仍然在回调中。
(请参阅bluebird ,了解我认为是最简单,因此也是最好的Promises
软件包,需要首先学习。)
(请注意, fs.readFileAsync
是不是一个错字。的一个非常有用的功能bluebird
是,它可以进行添加这个和其他基于承诺的版本fs
的现有功能,以标准的fs
对象,也懂得如何“承诺”其他模块,例如request
和mkdirp
)。
Generators
是上面教程中描述的其他功能,但是仅在新的,更新的但尚未正式发布的JavaScript版本(代号为“ Harmony”)中可用。
使用generators
还可以使您以更线性的方式编写代码,因为generators
提供的功能之一就是能够以不会对JavaScript事件循环造成严重破坏的方式等待异步操作的结果。 (但是正如我所说,它还不是通用功能。)
但是,您可以根据需要在当前版本的node中使用生成器,只需在节点命令行中添加“ --harmony”即可告诉它打开下一版本JavaScript的最新功能。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.