在循环完成nodejs请求之前执行的回调

callback executing before loop finishes nodejs request

我正在与 API 通信以获取有关 ID 数组的信息。因此,Array 中的每个 id 都需要对 api 的请求,如果它们符合逻辑,我想根据响应构建一个数据数组。但是,在构建新数组之前正在执行正在处理请求的函数的回调。在处理对 api 的大量调用时,我倾向于 运行 这个问题。我怎样才能解决这个具体的例子,将来解决这个问题的最佳方法是什么?

var request = require('request');
var _ = require('lodash');

var siteLayouts = [1550, 1552, 1554, 1556, 1558, 1560, 1562, 1564, 1566, 1568, 1570, 1572, 1574, 1730, 1734, 1736, 1738, 1740, 1896, 1898, 1900, 1902, 1904, 1906, 1908, 1910, 1914, 1922, 1924, 1926, 1928, 1930, 1932, 1934, 1936, 1938, 1940, 1942, 1944, 1946, 1948, 1950, 1952, 1954, 1956, 1958, 1960, 1962, 1964, 1966, 1968, 1970, 1972, 1974, 1976, 1978, 1980, 1984, 1986, 1988, 1990, 1992, 1994, 1996, 1998, 2000, 2002, 2004, 2006, 2008, 2010, 2012, 2014, 2016, 2020, 2022, 2030, 2032, 2034, 2036, 2038, 2040, 2042, 2044, 2046, 2048, 2052, 2054, 2056, 2060, 2062, 2064, 2066, 2068, 2070, 2072, 2122, 2124, 2148, 2154, 2156, 2270, 2272, 2274, 2374, 2418, 2688, 2692, 2968, 3756, 4094, 5122, 5524, 7326, 7494, 8704, 8886, 9226, 9232, 9234, 9236, 9238, 9830, 9836, 10052, 10054, 10056, 10999, 11083, 11085, 11429, 11513, 17279, 20397, 22285, 22287, 22289, 22291, 22293, 22295, 22807, 22809, 22811, 22813, 22815];

function getLayoutModules(siteLayouts, callback) {
    var matchedModules = [];
    for (var i = 0; i < siteLayouts.length; i++) {
        request('http://PRIVATE-API-URL/layout/' + siteLayouts[i], function(err, res, body) {
            if (!err && res.statusCode == 200) {
                var layoutModules = JSON.parse(body);
                var match = _.filter(layoutModules, {
                    'dtoLayoutModule': {
                        'ModuleName': 'Featured Content'
                    }
                });
                if (match.length > 0 && match[0].dtoLayoutModule) {
                    //console.log(match[0].dtoLayoutModule);
                    matchedModules.push(match[0].dtoLayoutModule);
                    console.log(matchedModules.length)
                }
            }
        });
    }
    callback(matchedModules);
}

getLayoutModules(siteLayouts, function(matchedModules) {
    console.log(matchedModules);
});

我已验证数据正在通过 console.log 长度添加到最终数组,但我首先看到回调 console.log,然后是长度。这也是使用 _.filter

过滤后来自请求的示例响应正文
[{
    RequestStatus: {
        StatusCode: '200',
        StatusTxt: 'Successful request.',
        Result: 'Successful request.',
        ValidationErrors: null
    },
    dtoLayoutModule: {
        Id: 116013,
        LayoutId: 10999,
        LayoutName: 'layout name',
        ModuleId: 7,
        ModuleName: 'Featured Content',
        DisplayName: 'name to display',
        Position: 4,
        Config: '<config><item name="layout" value="primary" /></config>',
        MaxContentCount: 4,
        CanInherit: false,
        IsInheritable: false,
        IsStaticModule: false
    }
}]

for 循环无法像您希望的那样使用异步代码,因为它们仅用于同步代码。使用 async.map 来处理每个 ID 的异步请求。这将获取您的 ID 数组,对每个 ID 执行一个异步请求,并返回一组响应,然后您可以使用常规 Array.prototype.filter 对其进行过滤,然后最终将 matchedModules 传递给您的回调。

你遇到的问题是同步思考,异步工作。您在循环中触发您的请求,并在循环之后立即调用您的回调。但是您不能保证在调用回调方法时所有调用都已处理。实际上,相反的情况是非常有保证的。您需要的是一种仅在处理完所有请求后才调用回调的方法。

我认为你应该考虑使用 promises。 Promises 是 Ecmascript6 的一部分,如今通过各种库被广泛使用。最著名的(据我所知)是q,但还有其他的。

promise 模式尝试(并且做得很好)消除所谓的 'callback hell'。

这是一个模式示例:

asyncCall()
  .then(function(data1){
     // do something...
     return anotherAsyncCall();
   })
   .then(function(data2){
     // do something...  
     return oneMoreAsyncCall();    
   })
   .then(function(data3){
      // the third and final async response
   })
   .fail(function(err) {
      // handle any error resulting from any of the above calls    
   })
   .done();

此示例摘自 this 名为 Promises 的文章——处理异步的另一种方法 JavaScript。

在您的特定情况下,一种解决方法是将承诺(为每个请求创建的每个承诺)收集到一个承诺数组中,然后使用 Q.all 方法。当所有承诺都已解决时,该承诺也已解决。