如何处理循环中的asynchronousNode.js

我有这样一个循环:

var i,j,temparray,chunk = 200; for (i=0,j=document.mainarray.length; i<j; i+=chunk) { temparray = document.mainarray.slice(i,i+chunk); var docs = collection.find({ id: { "$in": temparray}}).toArray(); docs.then(function(singleDoc) { if(singleDoc) { console.log("single doc length : " + singleDoc.length); var t; for(t = 0, len = singleDoc.length; t < len;t++) { fs.appendFile("C:/Users/x/Desktop/names.txt", singleDoc[t].name + "\n", function(err) { if(err) { return console.log(err); } }); } } }); } 

循环迭代两次。 在第一次迭代中获得了200个元素,第二次获得了130个元素。 当我打开.txt文件时,我只能看到130个名字。 我猜是因为Node.js的asynchronous性质,只处理数组的第二部分。 我应该怎么做才能处理数组的所有部分? 提前致谢。

编辑:我终于把这个代码:

 var generalArr = []; var i,j,temparray,chunk = 200; for (i=0,j=document.mainarray.length; i<j; i+=chunk) { temparray = document.mainarray.slice(i,i+chunk); generalArr.push(temparray); } async.each(generalArr, function(item, callback) { var docs = collection.find({ id: { "$in": item}}).toArray(); docs.then(function(singleDoc) { if(singleDoc) { console.log("single doc length : " + singleDoc.length); var t; for(t = 0, len = singleDoc.length; t < len;t++) { fs.appendFile("C:/Users/x/Desktop/names.txt", singleDoc[t].name + "\n", function(err) { if(err) { return console.log(err); } }); } } }); callback(null); }) 

当我改变这一行时:

 var docs = collection.find({ id: { "$in": item}}).toArray(); 

到这一行:

 var docs = collection.find({ id: { "$in": item}}).project({ name: 1 }).toArray(); 

它的作品,我可以打印所有的名字。 我猜测没有.project()时候内存有问题。 如何在不使用项目的情况下完成这项工作? 我应该改变一些内存限制吗? 提前致谢。

一旦你去asynchronous你不能回去 – 你的代码需要是asynchronous的。 在节点8中,您可以使用async处理并await关键字。 在旧版本中,你可以使用Promiseasync / await只是语法糖。

但是,节点中的大部分API都比Promise早,因此它们使用callback。 有一个promisify函数更新callback函数承诺。

有两种方法来处理这个问题,你可以让所有的asynchronous操作同时发生,或者你可以一个接一个地链接它们(保留顺序但花费更长的时间)。

所以, collection.find是asynchronous的,它需要一个callback函数或返回一个Promise 。 我将假设你使用的API是后者,但是你的问题可能是前者(在这种情况下查找promisify )。

 var findPromise = collection.find({ id: { "$in": item}}); 

现在,在这一点上findPromise持有正在运行的find操作。 我们说这是解决 (成功完成)或拒绝 (抛出错误)的承诺 。 我们希望排队完成一个行动,然后我们这样做:

 // The result of collection.find is the collection of matches findPromise.then(function(docs) { // Any code we run here happens asynchronously }); // Code here will run first 

在承诺的内部,我们可以返回更多的承诺(允许他们链接 – 完成一个asynchronous,然后完成下一个,然后Promise.all最后的决心,一旦完成)或使用Promise.all让他们都并行发生一旦完成:

 var p = new Promise(function(resolve, reject) { var findPromise = collection.find({ id: { "$in": item}}); findPromise.then(function(docs) { var singleDocNames = []; for(var i = 0; i < docs.length; i++) { var singleDoc = docs[i]; if(!singleDoc) continue; for(var t = 0; t < singleDoc.length; t++) singleDocNames.push(singleDoc[t].name); } // Resolve the outer promise with the final result resolve(singleDocNames); }); }); // When the promise finishes log it to the console p.then(console.log); // Code inline here will fire before the promise 

async / await节点8中,这容易:

 async function p() { // Await puts the rest of this function in the .then() of the promise const docs = await collection.find({ id: { "$in": item}}); const singleDocNames = []; for(var i = 0; i < docs.length; i++) { // ... synchronous code unchanged ... } // Resolve the outer promise with the final result return singleDocNames; }); // async functions can be treated like promises p().then(console.log); 

如果您需要将结果asynchronous写入文本文件,有几种方法可以实现 – 您可以等到最后写完所有的结果,或者链接一个承诺,在每次查找之后写入结果,尽pipe我find了并行IO操作往往会面临更多的死锁风险。

我认为你的代码是不必要的复杂,并且与内存计算相比,在一个循环中附加文件是非常昂贵的。 一个更好的方法是只写一次文件。

 var i, j, temparray, chunk = 200; for (i = 0, j = document.mainarray.length; i < j; i += chunk) { temparray = document.mainarray.slice(i, i + chunk); generalArr.push(temparray); } const queryPromises = []; generalArr.forEach((item, index) => { queryPromises.push(collection.find({ id: { "$in": item } }).toArray()); }); let stringToWrite = ''; Promise.all(queryPromises).then((result) => { result.forEach((item) => { item.forEach((element) => { //create a single string which you want to write stringToWrite = stringToWrite + "\n" + element.name; }); }); fs.appendFile("C:/Users/x/Desktop/names.txt", stringToWrite, function (err) { if (err) { return console.log(err); } else { // call your callback or return } }); }); 

在上面的代码中,我做了以下。

  1. 等待所有的数据库查询完成
  2. 让我们迭代这个列表并创build一个我们需要写入文件的string
  3. 写入文件

上面的代码有关于asynchronous控制stream的多个问题。 可能存在类似的代码,但只有在所有asynchronous操作中使用ES7asynchronous/等待操作符的情况下才可能存在。

当然,您可以轻松地通过承诺序列来实现解决scheme。 解:

 let flowPromise = Promise.resolve(); const chunk = 200; for (let i=0,j=document.mainarray.length; i<j; i+=chunk) { flowPromise = flowPromise.then(() => { const temparray = document.mainarray.slice(i,i+chunk); const docs = collection.find({ id: { "$in": temparray}}).toArray(); return docs.then((singleDoc) => { let innerFlowPromise = Promise.resolve(); if(singleDoc) { console.log("single doc length : " + singleDoc.length); for(let t = 0, len = singleDoc.length; t < len;t++) { innerFlowPromise = innerFlowPromise.then(() => new Promise((resolve, reject) => fs.appendFile( "C:/Users/x/Desktop/names.txt", singleDoc[t].name + "\n", err => (err ? reject(err) : resolve()) ) )); } } return innerFlowPromise; } }); } flowPromise.then(() => { console.log('Done'); }).catch((err) => { console.log('Error: ', err); }) 

当使用基于Promises的asynchronous控制stream程时,请记住每个循环和函数调用序列在执行asynchronous操作之前都不会暂停执行,因此手动包含所有序列。 或者使用async / await语法。

你正在使用哪个版本的nodejs? 您应该使用内置在新版本nodejs中的本地asynchronous/等待支持(不需要库)。 另外请注意, fs.appendFile是asynchronous的,所以你需要使用像promisify这样的库来将callback转化为承诺,或者只是使用appendFileSync并承受阻塞IO(但对你来说可能没问题,这取决于用例)。

 async function(){ ... for(var item of generalArr) { var singleDoc = await collection.find({ id: { "$in": item}}).toArray(); // if(singleDoc) { this won't do anything, since collection.find will always return something even if its just an empty array console.log("single doc length : " + singleDoc.length); var t; for(t = 0, len = singleDoc.length; t < len;t++){ fs.appendFileSync("C:/Users/x/Desktop/names.txt", singleDoc[t].name + "\n"); } }; } 
 var docs = collection.find({ id: { "$in": document.mainarray}}), // returns a cursor doc, names = [], toInsert; function saveToFile(cb) { toInsert = names.splice(0,100); if(!toInsert.length) return cb(); fs.appendFile("C:/Users/x/Desktop/names.txt", toInsert.join("\n"), cb); } (function process() { if(docs.hasNext()) { doc = docs.next(); doc.forEach(function(d) { names.push(d.name); }); if(names.length === 100) { // save when we have 100 names in memory and clear the memory saveToFile(function(err) { process(); }); } else { process(); } } else { saveToFile(function(){ console.log('All done'); }); } }()); // invoke the function 

如果使用核心模块和基本nodej不能解决您的问题,那么很可能是缺乏对如何工作的了解,或者对库(在本例中为FileSystem模块)的知识不足。

这里是你如何解决你的问题,没有第三方图书馆等。

 'use strict'; const fs = require('fs'); let chunk = 200; // How many rounds of array chunking we expect let rounds = Math.ceil(mainArray.length/chunk); // copy to temp (for the counter) let tempRounds = rounds; // set file name let filePath = './names.txt' // Open writable Stream let myFileStream = fs.createWriteStream(filePath); // from round: 0-${rounds} for (let i = 0; i < rounds; i++) { // assume array has ${chunk} elements left in this round let tempChunk = chunk; // if ${chunk} is to big ie i=3 -> chunk = 600 , but mainArray.length = 512 // This way we adjust the last round for "the leftovers" if (mainArray.length < i*chunk) tempChunk = Math.abs(mainArray.length - i*chunk); // slice it for this round let tempArray = mainArray.slice(i*chunk, i*chunk + tempChunk); // get stuff from DB let docs = collection.find({ id: { "$in": tempArray}}).toArray(); docs.then(function(singleDoc){ // for each name in the doc for (let j = 0; j < singleDoc.length; j++) { // write to stream myFileStream.write(singleDoc[t].name + "\n"); } // declare round done (reduce tempRounds) and check if it hits 0 if (!--tempRounds) { // if all rounds are done, end the stream myFileStream.end(); // BAM! you done console.log("Done") } }); } 

关键是要使用fs.WritableStreams 🙂 链接到文档