让 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 等待回调的主要内容,如果未能解决你的问题,请参考以下文章

完成时的 Foreach 回调[重复]

在 forEach 循环完成后运行回调函数

(JavaScript) 将 forEach 循环与内部回调同步

异步等待 Jquery ajax 回调?

使用 forEach 时避免回调多次调用

我们可以对 JavaScript forEach 异步执行回调吗? [复制]