【问题标题】:Making forEach wait for callback让 forEach 等待回调
【发布时间】: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 在完成之前前进到下一个元素。

【问题讨论】:

标签: javascript node.js


【解决方案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 来自哪里?
【解决方案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);

如果您对上述代码有任何疑问,请告诉我。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多