如何纠结Node.JS的asynchronous

我正在努力解决如何克服和处理Node.JS的asynchronous性质。 我已经做了相当多的阅读,并试图通过使用消息传递解决scheme或callback函数使Node做我想要的。

我的问题是我有一个对象,我想要构造函数来加载文件和填充数组。 然后我希望所有对这个函数的调用都使用这个加载的数据。 所以我需要原始的调用等待文件加载和所有后续的调用使用已经加载的私有成员。

我的问题是,加载数据和获取数据的函数正在执行asynchronous,即使它返回一个带callback函数。

不pipe怎样,有没有简单的我失踪? 还是有一个更简单的模式,我可以在这里使用? 这个函数应该返回加载文件的一部分,但是返回undefined。 我已检查该文件实际上是正在加载,并正常工作。

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); 

“我的问题是加载数据和获取数据的函数正在执行asynchronous,即使它返回一个带callback函数。

@AlbertoZaccagni我的意思是说,这条线是return this.loadVerses(input, this.getVerse); 在文件加载之前返回,当我期望它等待callback。

那是节点的工作原理,我会试着用一个例子来澄清它。

 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'); 

你正在阅读一个文件,并在控制台,你可能会有

  • 开始
  • 即将阅读…
  • 结束
  • …读!

你可以单独尝试一下,看看它是否在行动中,并调整它来理解这一点。 这里需要注意的是,您的代码将继续运行,直到读取文件时才跳过callback的执行。

只是因为你声明了一个callback并不意味着执行将暂停,直到callback被调用,然后恢复。

所以这是我如何改变这个代码:

 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); }); 

我忽略了

  1. 事实上,我们不把这个错误视为callback的第一个参数
  2. 这个足够快的速度会创造竞赛条件,但我相信这是另外一个问题

[收集到一个答案,并从我以前的评论扩大]

TL; DR您需要构造代码,以便任何操作的结果仅在该操作的callback中使用,因为您无法在其他任何地方访问它。

虽然将它分配给一个外部的全局variables肯定会按预期工作,但是这样做只会在callback触发后才会发生,而这种情况是无法预测的

评论

callback不会返回值,因为它们本质上是在将来某个时候执行的。

一旦你将一个callback函数传递给一个控制的asynchronous函数,当周围的函数决定调用它的时候它将被执行。 你不控制这个,所以等待返回的结果是行不通的。

你的例子代码, song.verse(1); 不能期望返回任何有用的东西,因为它被立即调用,并且由于callback还没有开始,只会返回唯一的值: null

我担心这种依赖于通过callback的asynchronous函数是NodeJS如何运行的一个不可改变的特性。 它是它的核心

不要灰心。 对这里的所有NodeJS问题的一个快速调查非常清楚地表明,只有在callback中必须使用asynchronous操作的结果才是理解如何在NodeJS中编程的最大障碍。

有关正确构造NodeJS代码的各种方法,请参阅使用Promise,Generators和其他方法pipe理Node.jscallback函数 。

我相信它清楚简洁地描述了您所面临的问题,并提供了几种正确重构代码的方法。

那里提到的两个特性,Promise和Generators,都是编程特性/概念,我相信这对你有很大的用处。

Promises (或者像某些人称之为Futures )是一种编程抽象,允许人们在代码中更线性地编写代码。

 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 */ }); 

实际上,Promise只是一种以更线性的方式编组标准callback金字塔的手段。 (就我个人而言,我相信他们需要一个新的名字,因为“未来可能出现的一些有价值的承诺”的想法不是一个容易理解的东西,至less它不适合我)。

承诺通过(幕后)重组“回拨地狱”这样做:

 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”callback的唯一参数,任何错误都通过rejected()传递,所以它可以被任何.catch(function(err){ ... })捕获.catch(function(err){ ... })你定义的处理程序。

承诺也可以完成所有你期望的(比较标准的) async模块的事情,比如串联或并行地运行callback,并且对数组元素进行操作,一旦收集到所有的结果,回收它们的callback结果。

但是你会注意到Promise并没有完全做你想做的事情,因为一切还在callback中。

(请参阅bluebird,因为我认为这是最简单的,因此也是最先学习的Promises包)。

(注意, fs.readFileAsync不是拼写错误, bluebird一个有用特性是可以将fs现有函数的这个和其他基于Promises的版本添加到标准的fs对象,同时也理解如何“ promisify“其他模块,如requestmkdirp )。

Generators是上述教程中描述的另一个特性,但只能在新的,更新但尚未正式发布的JavaScript版本(代号为“Harmony”)中使用。

使用generators也可以让你以更线性的方式编写代码,因为它提供的function之一就是能够以不会对JavaScript事件循环造成严重破坏的方式等待asynchronous操作的结果。 (但正如我所说,这不是一般用途的function。)

但是,如果您愿意,您可以在当前版本的节点中使用生成器,只需将“–harmony”添加到节点命令行中,告诉它打开下一个JavaScript版本的最新function即可。