Node.js 同步循环或迭代异步语句

Posted

技术标签:

【中文标题】Node.js 同步循环或迭代异步语句【英文标题】:Node.js synchronously loop or iterate over asynchronous statements 【发布时间】:2014-07-04 19:40:03 【问题描述】:

我想为每个循环做一个但让它同步运行。循环的每次迭代都会执行一次 http.get 调用,并返回 json 以将值插入数据库。问题是for循环异步运行,导致所有http.gets同时运行,我的数据库最终没有插入所有数据。我正在使用async-foreach来尝试做我想做的事这样做,但如果我能以正确的方式做到这一点,我就不必使用它。

mCardImport = require('m_cardImport.js');
var http = require('http');
app.get('/path/hi', function(req, res) 

mCardImport.getList(function(sets) 
  forEach(sets, function(item, index, arr) 
    theUrl = 'http://' + sets.set_code + '.json';
    http.get(theUrl, function(res) 

      var jsonData = '';
      res.on('data', function(chunk) 
        jsonData += chunk;
      );

      res.on('end', function() 
        var theResponse = JSON.parse(jsonData);
        mCardImport.importResponse(theResponse.list, theResponse.code, function(theSet) 
          console.log("SET: " + theSet);
        );
      );
    );
  );
);
);

还有我的模特

exports.importResponse = function(cardList, setCode, callback) 

mysqlLib.getConnection(function(err, connection) 

forEach(cardList, function(item, index, arr) 

  var theSql = "INSERT INTO table (name, code, multid, collector_set_num) VALUES "
   + "(?, ?, ?, ?) ON DUPLICATE KEY UPDATE id=id";
  connection.query(theSql, [item.name, setCode, item.multid, item.number], function(err, results) 
    if (err) 
      console.log(err);
    ;
  );
);
);
callback(setCode);
;

【问题讨论】:

您可以使用async.each 尽管鉴于您发布的内容,您应该能够异步/并行执行此操作,所以我想弄清楚为什么您的所有数据都不是插入。没有理由无缘无故地放慢速度。 我也不知道为什么。但我预计大约有 24,000 条记录,而且它的记录要少得多。当我截断表格并重新运行它时,它类似于 3,000 或 5,000 这个数字并不总是相同的。所以我认为我在太短的时间内抛出了太多的 http.get 请求和/或太多的 MYSQL 调用,导致事情被遗漏/丢弃。 @Joe 我要感谢你建议我重新查看我的代码,我发现我没有在每个 export.importResponse 的末尾明确调用 connection.release() ()。我认为 connection.release() 是自动调用的,但是当我再次检查并明确添加它时,它现在可以正常工作,并且所有 24,000 条记录都按预期添加。再次感谢! 【参考方案1】:

要循环和同步链接异步操作,最简洁的解决方案可能是使用 Promise 库(ES6 中引入了 Promise,这是要走的路)。

使用Bluebird,这可能是

Var p = Promise.resolve();
forEach(sets, function(item, index, arr) 
    p.then(new Promise(function(resolve, reject) 
         http.get(theUrl, function(res) 
         ....
         res.on('end', function() 
              ...
              resolve();
         
    ));
);
p.then(function()
   // all tasks launched in the loop are finished
);

【讨论】:

所以循环迭代开始,传递给p.then,循环暂停,一旦p.then返回下一个循环迭代开始?我理解正确吗? 不完全是:循环本身不会暂停。但是您使用 then 传递的回调只会一个接一个地被调用。 所以如果我使用 for(let i=0;i 【参考方案2】:

使用递归,代码非常干净。等待 http 响应返回,然后启动下一次尝试。这适用于所有版本的节点。

var urls = ['http://***.com/', 'http://security.stackexchange.com/', 'http://unix.stackexchange.com/'];

var processItems = function(x)
  if( x < urls.length ) 
    http.get(urls[x], function(res) 

      // add some code here to process the response

      processItems(x+1);
    );
  
;

processItems(0);

使用 Promise 的解决方案也可以很好地工作,而且更简洁。例如,如果你有一个version of get that returns a promise 和 Node v7.6+,你可以像这个例子一样编写一个 async/await 函数,它使用了一些新的 JS 特性。

const urls = ['http://***.com/', 'http://security.stackexchange.com/', 'http://unix.stackexchange.com/'];

async function processItems(urls)
  for(const url of urls) 
    const response = await promisifiedHttpGet(url);    
    // add some code here to process the response.
  
;

processItems(urls);

注意:这两个示例都跳过了错误处理,但您可能应该在生产应用程序中使用它。

【讨论】:

真的是一个很好的答案。我正在弄清楚如何使 for 循环同步。它就像一个魅力:) var i = 0;的需要吗? @betweenbrain 哈哈,不错。我很惊讶直到现在没有其他人注意到。固定。 这对于较大的数据集可能会出现问题,因为一旦迭代完成,内存将不会被释放 @HarshaGoli 我同意,但这是一个合理的起点。如果需要大规模,除非仔细优化,否则递归总是会出现内存问题。【参考方案3】:

我发现每次调用完成后我都没有释放我的 mysql 连接,这会导致连接失败并且似乎是同步问题。

在显式调用 connection.release(); 后,它使我的代码即使以异步方式也能 100% 正确工作。

感谢为这个问题发帖的人。

【讨论】:

请注意,javascript 不提供尾递归,只有在递归深度有硬性限制时才应使用递归函数。如果你不知道这意味着什么,并且你知道你会有很多循环,那就用 promise 吧:)【参考方案4】:
"use strict";

var Promise = require("bluebird");
var some = require('promise-sequence/lib/some');

var pinger = function(wht) 
    return new Promise(function(resolve, reject) 
        setTimeout(function ()  

            console.log('I`ll Be Waiting: ' + wht);
            resolve(wht);

        , Math.random() * (2000 - 1500) + 1500);
    );


var result = [];
for (var i = 0; i <= 12; i++) 
    result.push(i);


some(result, pinger).then(function(result)
  console.log(result);
);

【讨论】:

【参考方案5】:
var urls = ['http://***.com/', 'http://security.stackexchange.com/', 'http://unix.stackexchange.com/'];

for (i = 0; i < urls.length; i++)
   http.get(urls[i], function(res) 
       // add some code here to process the response
   );

【讨论】:

您应该在您的代码中添加一些解释,以便其他人可以从中学习 这不会按预期工作,因为 for 循环将在异步 http.get 调用之前完成。这种方式也不能保证操作的顺序。如果有一天***很慢,它可能会在所有其他url之后返回结果。【参考方案6】:

只需将循环包装在 async 函数中。这个例子说明了我的意思:

const oneSecond = async () => 
    new Promise((res, _) => setTimeout(res, 1000));

这个功能在 1 秒后完成:

const syncFun = () => 
    for (let i = 0; i < 5; i++) 
        oneSecond().then(() => console.log(`$i`));
    


syncFun(); // Completes after 1 second ❌

这个按预期工作,5秒后完成:

const asyncFun = async () => 
    for (let i = 0; i < 5; i++) 
        await oneSecond();
        console.log(`$i`);
    


asyncFun(); // Completes after 5 seconds ✅

【讨论】:

以上是关于Node.js 同步循环或迭代异步语句的主要内容,如果未能解决你的问题,请参考以下文章

node.js 如何决定一个语句是不是被异步处理?

Node.js 异步数组迭代

PURE Javascript 是同步的还是异步的?

node 常用操作总结

node.js中的forEach是同步还是异步

node.js 回调函数事件循环EventEmitter ar