簡體   English   中英

如何在循環中處理異步Node.js.

[英]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
    }
  });
});

在上面的代碼中,我執行以下操作。

  1. 等待所有數據庫查詢完成
  2. 讓迭代遍歷此列表並創建一個我們需要寫入文件的字符串
  3. 寫入文件

一旦你去異步你就不能回去了 - 你的所有代碼都需要是異步的。 在節點8中,您可以使用asyncawait關鍵字來處理此問題。 在舊版本中你可以使用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")
        }
    });
}

關鍵是在這里使用fs.WritableStreams :) 鏈接到docs

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM