[英]How to handle async Node.js in a loop
我有这样一个循环:
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的异步性质,只处理数组的第二部分。 我该怎么做才能处理数组的所有部分? 提前致谢。
编辑:我终于将代码转为:
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()
情况下尝试内存有问题。 如何在不使用项目的情况下完成这项工作? 我应该更改一些内存限制吗? 提前致谢。
我认为你的代码是不必要的复杂,并且与内存计算相比,在循环中附加文件非常昂贵。 更好的方法是只写一次文件。
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
}
});
});
在上面的代码中,我执行以下操作。
一旦你去异步你就不能回去了 - 你的所有代码都需要是异步的。 在节点8中,您可以使用async
和await
关键字来处理此问题。 在旧版本中你可以使用Promise
- async
/ await
只是它的语法糖。
但是,节点中的大多数API都比Promise
早,因此它们使用回调。 有一个promisify
函数可以将回调函数更新为promises。
有两种方法可以处理这种情况,您可以让所有异步操作同时发生,或者您可以将它们一个接一个地链接起来(保留顺序但需要更长时间)。
因此, collection.find
是异步的,它需要一个回调函数或返回一个Promise
。 我将假设您使用的API执行后者,但您的问题可能是前者(在这种情况下查找promisify
)。
var findPromise = collection.find({ id: { "$in": item}});
现在,此时findPromise
保持正在运行的find
操作。 我们说这是一个解析 (成功完成)或拒绝 (抛出错误)的承诺 。 我们要排队一个动作做一次它完成,然后我们做到这一点与then
:
// 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
在承诺内部,我们可以返回进一步的承诺(允许它们被链接 - 完成一个异步,然后完成下一个,然后一旦完成就解雇最终解决方案)或使用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
这是在节点8与容易得多 async
/ await
:
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);
如果你需要异步地将结果写入文本文件,有几种方法可以做到 - 你可以等到最后并写下所有这些,或者在每次查找后链接一个promise来写它们,尽管我发现并行IO操作往往更容易陷入僵局。
上面的代码有关于异步控制流的多个问题。 可能存在类似的代码,但仅限于在所有异步操作中使用ES7 async / await运算符的情况。
当然,您可以通过promises序列轻松实现解决方案。 解:
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);
})
当使用异步般的控制流程,根据承诺,永远记住,每一个循环和函数调用序列不会暂停执行,直到异步操作完成,因此包括所有then
手动序列。 或者使用async / await语法。
您使用的是哪个版本的nodejs? 您应该使用内置于较新版本nodejs的本机async / await支持(无需库)。 另请注意, fs.appendFile
是异步的,因此您需要使用像promisify
这样的库将回调转换为promise或者只使用appendFileSync
并遭受阻塞IO(但可能对您appendFileSync
,具体取决于用例。)
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
如果使用核心模块和基本nodejs无法解决问题,则很可能缺乏对工作原理或对库的知识不足的理解(在本例中为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 i.e. 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")
}
});
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.