让 forEach 等待回调
Posted
技术标签:
【中文标题】让 forEach 等待回调【英文标题】:Making forEach wait for callback 【发布时间】:2017-05-01 22:55:27 【问题描述】:异步编程新手,所以我不知道该怎么做:
$results = [];
products.forEach(function (product)
// 1. Search ...
google(keyword, function (err, res)
if (err) console.error(err)
for (var i = 0; i < res.links.length; ++i)
var result = res.links[i];
var obj =
title: res.links[i].title,
href: res.links[i].href,
description: res.links[i].description
results.push(obj); // 2. store each result in results Array
, processData); // 3. send all results to processData when done
// 5. NOW, itereate further ...
);
function processData(results)
console.log('processing data');
// 4. save results to DB
由于该过程需要发出 HTTP 请求、收集数据然后保存到数据库,这都需要时间,所以我不希望 forEach 在完成之前前进到下一个元素。
【问题讨论】:
***.com/questions/5010288/… 或 ***.com/questions/18983138/… 的潜在重复? 【参考方案1】:使用async 包。
async.eachSeries(docs, function iteratee(product, callback)
// 1. Search ...
google(keyword, function (err, res)
if (err)
console.error(err)
callback(results) // this will send a fail callback.
for (var i = 0; i < res.links.length; ++i)
var result = res.links[i];
var obj =
title: res.links[i].title,
href: res.links[i].href,
description: res.links[i].description
results.push(obj); // 2. store each result in results Array
callback(null, results) // this is a success callback
, processData); // 3. send all results to processData when done
);
注意:回调的行为类似于返回。一旦回调满足该值,它就不会进一步进行。现在它将发送下一个产品的请求。
【讨论】:
举个例子就好了。【参考方案2】:由于 forEach 是同步的并且请求是异步的,因此无法完全按照您的描述进行操作。但是,您可以做的是创建一个函数来处理 docs 数组中的一项并将其删除,然后当您完成处理后,转到下一个:
var results;
var productsToProcess;
MongoClient.connect( 'mongodb://localhost:27017/suppliers', function ( err, db )
assert.equal( null, err );
var findDocuments = function ( db )
var collection = db.collection( 'products' );
collection.find(
$and: [
"qty":
$gt: 0
,
"costex":
$lte: 1000.0
]
,
"mpn": 1,
"vendor": 1,
"_id": 0
).limit( 1 ).toArray( function ( err, products )
assert.equal( err, null );
productsToProcess = products;
getSearching();
db.close();
);
findDocuments( db );
);
function getSearching()
if ( productsToProcess.length === 0 ) return;
var product = productsToProcess.splice( 0, 1 )[0];
var keyword = product[ 'vendor' ] + ' "' + product[ 'mpn' ] + '"';
google( keyword, function ( err, res )
if ( err ) console.error( err )
for ( var i = 0; i < res.links.length; ++i )
var result = res.links[ i ];
var obj =
title: res.links[ i ].title,
href: res.links[ i ].href,
description: res.links[ i ].description
results.push( obj );
, processData );
function processData( results )
MongoClient.connect( 'mongodb://localhost:27017/google', function ( err, db )
assert.equal( null, err );
// insert document to DB
var insertDocuments = function ( db, callback )
// Get the documents collection
var collection = db.collection( 'results' );
// Insert some documents
collection.insert( results, function ( err, result )
assert.equal( err, null );
console.log( "Document inserted" );
callback( result );
db.close();
);
insertDocuments( db, getSearching );
);
编辑
将产品从数据库移至productsToProcess
变量,并将getSearching()
更改为不再需要参数。
【讨论】:
keyword
不是数组,product
是。
是的,有些事情不是很清楚,比如docs
变量是什么,keyword
来自哪里以及为什么从未使用过product
参数。我认为您仍然可以使示例适合您的情况。
仍然令人困惑,但据我所知..它是一种递归而不是循环
是的,通过将其设为递归函数,您可以手动确定下一次迭代何时开始,允许您在上一次迭代的处理完成时启动它。在您的示例中,keyword
来自哪里?
pastebin.com/pZg3F5Jy
【参考方案3】:
你不能等待Array.prototype.forEach()
内部的异步操作。
考虑到您用于向 Google 发出请求的库与 Promise 不兼容,可能在您的情况下使用 Async 可能是一个快速的解决方案(对于大型项目或与 Promise 兼容的库,我建议使用 Promise 方式)。
Async map 允许您在内部使用异步操作,因为它等待回调。
在你的情况下,我猜是这样的:
async.map(products, function(product, callback)
var keyword = product['vendor'] + ' "' + product['mpn'] + '"';
google( keyword, function (err,res)
if (err)
// if it fails it finish here
return callback(err);
// using map here makes it easier to loop through the results
var results = res.links.map(function(link)
return
title: link.title,
href: link.href,
description: link.description
;
);
callback(null, results);
);
, processData);
如果您对上述代码有任何疑问,请告诉我。
【讨论】:
以上是关于让 forEach 等待回调的主要内容,如果未能解决你的问题,请参考以下文章