程序失败,因为readFile是asynchronous的?

我试图把两个列表结合起来。 List1有681个法语动词,List2有681个翻译。 我正在使用javascript和node.js来读取文件。 这是我的尝试:

var frenchWords, englishWords, combinedList; fs = require('fs') // 1. read the french file fs.readFile('frenchVerbsList.txt', 'utf8', function (err,data) { if (err) { return console.log("ERROR here!: " + err); } frenchWords = data.split('\n'); }); //read the english file fs.readFile('englishVerbsList.txt', 'utf8', function (err,data2) { if (err) { return console.log("ERROR here!: " + err); } englishWords = data2.split('\n'); }); // 2. combine the lists //*** it fails here, I'm guessing it's because the readFile operation hasn't finished yet. var combinedList; for(i=0; i<frenchWords.length; i++){ combinedList[i] = frenchWords[i] + ",,," + englishWords[i]; } // 3. check the result for(i=0; i<10; i++){ console.log(combinedList[i]); } 

非常感谢任何帮助,我正在这样做,以保持我的头脑活跃:-)

你是正确的, fs.readFile()callback的asynchronous性质导致你的问题。

这些callback将在未来不确定的时间被调用,而其余的代码将继续运行。 由于node.js的事件驱动devise,直到代码的其余部分执行完毕,才会调用callback。 因此,保证您在尝试使用englishWordsfrenchWordsvariables之前,他们有任何结果。

你有一堆不同的select:

切换到fs.readFileAsync(在大多数情况下不推荐)

你可以切换到使用fs.readFileSync() 。 这是最简单的改变,因为你目前的控制stream程是可行的。 但是,这通常不build议用于node.js开发,因为它对服务器的使用效率不高。 如果这个代码在一个服务器进程中可以/应该保持在等待一个文件被读取时做其他事情的能力,那么fs.readFileSync()将会fs.readFileSync()可伸缩性。 如果这只是一个一次性的脚本(不是一个加载的服务器),那么fs.readFileSync()可能工作得很好。 但是,你可能应该学习更好的“node.js风格”等待编码与asynchronous操作(见下面的选项)。

通过在callback内持续stream动来序列化操作

您可以使用嵌套序列化asynchronous操作。 这涉及到只在asynchronouscallback中继续处理逻辑。 这样,你就知道你需要的结果可供你继续处理。 这看起来像这样:

 const fs = require('fs') // read the french file fs.readFile('frenchVerbsList.txt', 'utf8', function (err, data) { if (err) { return console.log("ERROR here!: " + err); } var frenchWords = data.split('\n'); //read the english file fs.readFile('englishVerbsList.txt', 'utf8', function (err, data2) { if (err) { return console.log("ERROR here!: " + err); } var englishWords = data2.split('\n'); // 2. combine the lists var combinedList = []; for (i = 0; i < frenchWords.length; i++) { combinedList[i] = frenchWords[i] + ",,," + englishWords[i]; } // 3. check the result for (i = 0; i < 10; i++) { console.log(combinedList[i]); } }); }); 

对两个asynchronous操作完成时手动编码检查

上面的serialize选项有一个缺点,就是它在启动下一个asynchronous操作之前等待第一个asynchronous操作完成。 这并不理想,因为两个asynchronous操作可以并行运行(更快的最终结果)。 以下所有选项将是并行运行asynchronous操作的不同方式,并在两者都完成时进行监视,以便触发最终处理。 这是手动监视选项。 在加载法语和英语单词的完成callback中,检查另一个是否也完成。 如果是,则调用一个函数来处理结果。 由于一次只能完成一个,只要没有错误,其中一个将完成第二个,并将调用你的函数来处理结果:

 var frenchWords, englishWords; fs = require('fs') // read the french file fs.readFile('frenchVerbsList.txt', 'utf8', function (err, data) { if (err) { return console.log("ERROR here!: " + err); } frenchWords = data.split('\n'); if (frenchWords && englishWords) { processResults(); } }); //read the english file fs.readFile('englishVerbsList.txt', 'utf8', function (err, data2) { if (err) { return console.log("ERROR here!: " + err); } englishWords = data2.split('\n'); if (frenchWords && englishWords) { processResults(); } }); function processResults() { // combine the lists var combinedList = []; for (let i = 0; i < frenchWords.length; i++) { combinedList[i] = frenchWords[i] + ",,," + englishWords[i]; } // check the result for (let i = 0; i < 10; i++) { console.log(combinedList[i]); } } 

使用ES6承诺来监视您的asynchronous操作

使用ES6,promise已经成为Javascript规范的一个标准部分,它们是协调多个asynchronous操作的一个很好的方法,它们也可以更直接地进行适当的error handling(特别是在复杂的情况下)。 要在这里使用promise,首先需要创buildfs.readFile()的“promisified”版本。 这将是一个使用承诺而不是普通callback的包装函数。 然后,可以使用Promise.all()来协调何时完成两个asynchronous操作。

 var fs = require('fs'); // promise wrapper fs.readFileAsync = function(file, encoding) { return new Promise(function(resolve, reject) { fs.readFile(file, encoding, function(err, data) { if (err) return reject(err); resolve(data); }); }); } // common helper function function readFileSplitWords(file) { return fs.readFileAsync(file, 'utf8').then(function(data) { // make split words be the fulfilled value of the promise return data.split('\n'); }); } var frenchPromise = readFileSplitWords('frenchVerbsList.text'); var englishPromise = readFileSplitWords('englishVerbsList.txt'); Promise.all([frenchPromise, englishPromise]).then(function(results) { // combine the lists var frenchWords = results[0], englishWords = results[1]; var combinedList = []; for (i = 0; i < frenchWords.length; i++) { combinedList[i] = frenchWords[i] + ",,," + englishWords[i]; } // check the result for (i = 0; i < 10; i++) { console.log(combinedList[i]); } }, function(err) { // handle an error here }); 

使用承诺库扩展承诺function

ES6承诺是非常有能力的,但有一些非常有用的function,使用某些第三方库添加的承诺。 我个人使用蓝鸟图书馆。 以下是使用Bluebird库的以前的选项:

 const Promise = require('bluebird'); const fs = Promise.promisifyAll(require('fs')); // common helper function function readFileSplitWords(file) { return fs.readFileAsync(file, 'utf8').then(function(data) { // make split words be the fulfilled value of the promise return data.split('\n'); }); } var frenchPromise = readFileSplitWords('frenchVerbsList.text'); var englishPromise = readFileSplitWords('englishVerbsList.txt'); Promise.all([frenchPromise, englishPromise]).spread(function(frenchWords, englishWords) { // combine the lists var combinedList = []; for (i = 0; i < frenchWords.length; i++) { combinedList[i] = frenchWords[i] + ",,," + englishWords[i]; } // check the result for (i = 0; i < 10; i++) { console.log(combinedList[i]); } }, function(err) { // handle an error here }); 

这使用Bluebird的Promise.promisifyAll()来自动制作fs库中所有方法的promisified版本(非常有用)。 而且,它使用.spread()方法而不是.spread()来自动将两个结果分隔到其命名参数中。

使用更多的扩展function来处理任意数组的文件名

你也可以使用更多的扩展蓝鸟function,例如处理数组的Promise.all() ,然后对所得到的promise(上面的代码手动完成)做Promise.all() )。 这样,你就可以使文件名成为你想要的任何语言的文件名的任意列表,并且代码可以在这方面变得更通用:

 const Promise = require('bluebird'); const fs = Promise.promisifyAll(require('fs')); // common helper function function readFileSplitWords(file) { return fs.readFileAsync(file, 'utf8').then(function(data) { // make split words be the fulfilled value of the promise return data.split('\n'); }); } var files = ['frenchVerbsList.text', 'englishVerbsList.txt']; Promise.map(files, readFileSplitWords).then(function(results) { // results is an array of arrays where each sub-array is a language list of words // combine the lists (assumes all word lists have the same length) var combinedList = []; var len = results[0].length; // for each word in the first array for (var i = 0; i < len; i++) { // get all the other words in the same array position var words = []; for (var j = 0; j < results.length; j++) { words.push(results[j][i]); } combinedList.push(words.join(',,,')); } // check the result for (i = 0; i < 10; i++) { console.log(combinedList[i]); } }, function(err) { // handle an error here }); 

使用一些库,例如async ,等待两个任务(读取文件)完成,然后继续。

或者使用readFileSync (但在大多数情况下,使用asynchronous版本是首选,除非它只是一个本地“工具”脚本,而不是服务器的一部分)。

你的猜测是正确的,当你到达你的代码的组合部分时,没有什么能够保证文件的读取完成,没有任何事情保证这两个文件都以相同的速度处理。

你需要做的是确保两个文件在组合列表之前完成。 你可以通过使用asynchronous库来实现。 其中一个函数是并行的 ,除了两个函数(在你的情况下,每个文件一个函数)并行运行它们,还有一个callback函数,一旦完成