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 同步循环或迭代异步语句的主要内容,如果未能解决你的问题,请参考以下文章