【问题标题】:How to return single promise after for loop (which produces a promise on every iteration) is complete?如何在 for 循环(每次迭代都会产生一个 Promise)完成后返回单个 Promise?
【发布时间】:2016-01-05 00:40:37
【问题描述】:

我的 promise 返回码有问题,我有一个函数 getTagQuotes,其中包含一个 for 循环,可以多次调用 API 以将数据返回到数组中。

我的代码如何从下面开始:

// If there are tags, then wait for promise here:
if (tags.length > 0) {

    // Setting promise var to getTagQuotes:
    var promise = getTagQuotes(tags).then(function() {
        console.log('promise =',promise);

        // This array should contain 1-3 tags:
        console.log('tweetArrayObjsContainer =',tweetArrayObjsContainer);

        // Loop through to push array objects into chartObj:
        for (var i=0; i<tweetArrayObjsContainer.length; i++) {
            chartObj.chartData.push(tweetArrayObjsContainer[i]);
        }

        // Finally draw the chart:
        chartDirective = ScopeFactory.getScope('chart');
        chartDirective.nvd3.drawChart(chartObj.chartData);
    });
}

我的 getTagQuotes 函数返回承诺:

function getTagQuotes(tags) {
    var deferred = $q.defer(); // setting the defer
    var url      = 'app/api/social/twitter/volume/';

    // My for loop, which only returns ONCE, even if there are 3 tags
    for (var i=0; i<tags.length; i++) {
        var loopStep = i;
        rawTagData   = [];

        // The return statement
        return GetTweetVolFactory.returnTweetVol(url+tags[i].term_id)
            .success(function(data, status, headers, config) {
                rawTagData.push(data);

                // One the last loop, call formatTagData
                // which fills the tweetArrayObjsContainer Array
                if (loopStep === (rawTagData.length - 1)) {
                    formatTagData(rawTagData);
                    deferred.resolve();
                    return deferred.promise;
                }
            });
    }

    function formatTagData(rawData) {

        for (var i=0; i<rawData.length; i++) {
            var data_array = [];
            var loopNum = i;

            for (var j=0; j<rawData[loopNum].frequency_counts.length; j++) {
                var data_obj = {};
                data_obj.x = rawData[loopNum].frequency_counts[j].start_epoch;
                data_obj.y = rawData[loopNum].frequency_counts[j].tweets;
                data_array.push(data_obj);
            }

            var tweetArrayObj = {
                "key" : "Quantity"+(loopNum+1), "type" : "area", "yAxis" : 1, "values" : data_array
            };

            tweetArrayObjsContainer.push(tweetArrayObj);
        }
    }
}

注意这一行

return GetTweetVolFactory.returnTweetVol(url+tags[i].term_id)

它在我的 for 循环中:

for (var i=0; i<tags.length; i++)

如果我只需要循环一次,一切都会很好。但是,一旦有另一个标签(最多 3 个),它仍然只返回第一个循环/数据。它不会等到 for 循环完成。然后回报承诺。所以我的tweetArrayObjsContainer 总是只有第一个标签。

【问题讨论】:

  • for 循环内的return 从整个getTagQuotes 函数返回。 return 将从当前函数返回,不管你是在 for 循环还是 while 循环或其他什么。
  • 我在玩这个,如果我从getTagQuotes 中删除返回,那么我在getTagQuotes(tags).then(function() 上得到.then of function undefined 错误
  • 当然,因为你没有返回任何东西。看起来你有 3 个项目,所有这些项目都返回承诺。并且您希望等待所有承诺完成后再采取行动。
  • 你应该返回 deferred.promise;而不是 GetTweetVolFactory.returnTweetVol

标签: javascript angularjs for-loop promise angular-promise


【解决方案1】:

参考这个问题和earlier question

  • 在几个地方,使用array.map() 代替for 循环,代码通常会更简洁。
  • getTagQuotes() 将通过构建一系列 Promise、提交给 $q.all() 并返回一个聚合 Promise 来变得更简洁。
  • formatTagData() 及其与调用者的关系将通过返回转换后的 rawData 变得更清晰。

有了一些假设,代码应该简化成这样:

getTagQuotes(tags).then(function(tweetArrayObjsContainer) {
    chartObj.chartData = chartObj.chartData.concat(tweetArrayObjsContainer); // concat() ...
    // chartObj.chartData = tweetArrayObjsContainer;                         // ... or simply assign??
    chartDirective = ScopeFactory.getScope('chart');
    chartDirective.nvd3.drawChart(chartObj.chartData);
});

function getTagQuotes(tags) {
    var url = 'app/api/social/twitter/volume/';
    var promises = tags.map(function(tag) {
        var deferred = $q.defer();
        GetTweetVolFactory.returnTweetVol(url + tag.term_id)
        .success(function(data, status, headers, config) {
            deferred.resolve(data);
        })
        .error(function(data, status) {
            console.log(tag.term_id + ': error in returning tweet data');
            deferred.resolve(null); // resolve() here effectively catches the error
        });
        return deferred.promise;
    });
    return $q.all(promises).then(formatTagData); //this is much much cleaner than building an explicit data array and resolving an outer deferred.

    function formatTagData(rawData) {
        return rawData.filter(function(data) {
            return data || false; // filter out any nulls
        }).map(function(item, i) {
            return {
                'key': 'Quantity' + (i+1),
                'type': 'area',
                'yAxis': 1,
                'color': tagColorArray[i],
                'values': item.frequency_counts.reverse().map(function(c) {
                    return {
                        x: addZeroes(c.start_epoch),
                        y: c.tweets,
                    };
                })
            };
        });
    }
}

【讨论】:

  • 有趣的方法,我刚刚尝试过。但看起来我现在还需要在我的 returnTweetVol 函数中添加另一个承诺链。
  • 这听起来(部分?)是个好主意。理想情况下,returnTweetVol() 将返回一个承诺,以便您可以编写 GetTweetVolFactory.returnTweetVol(url + tag.term_id).then(...),避免像我的回答那样即时承诺。
【解决方案2】:

你应该在这里返回一个promise数组,这意味着你应该像这样改变getTagsQuotes:

function getTagQuotes(tags) {

    var url      = 'app/api/social/twitter/volume/',
        promises = [];

    for (var i=0; i<tags.length; i++) {

       promises.push( GetTweetVolFactory.returnTweetVol( url+tags[i].term_id ) );

    }

    return promises;
}

然后像这样循环这个promise:

if (tags.length > 0) {

    var promises = getTagQuotes(tags);

    promises.map( function( promise ) {

         promise.then( function( data ) { 

            //Manipulate data here

         });

    });
}

编辑:如果您希望按照评论中所述完成所有承诺,您应该这样做:

if (tags.length > 0) {

    Promise.all( getTagQuotes(tags) ).then( function( data ) { 

        //Manipulate data here

    });
}

编辑:完整的数据操作:

Promise.all( getTagQuotes(tags) ).then( function( allData ) {

allData.map( function( data, dataIndex ){

    var rawData = data.data,
        dataLength = rawData.frequency_counts.length,
        j = 0,
        tweetArrayObj = {
            // "key"    : "Quantity"+(i+1),
            // "color"  : tagColorArray[i],
            "key"    : "Quantity",
            "type"   : "area",
            "yAxis"  : 1,
            "values" : []
        };

    for ( j; j < dataLength; j++ ) {

        rawData.frequency_counts[j].start_epoch = addZeroes( rawData.frequency_counts[j].start_epoch );

        tweetArrayObj.values.push( { x:rawData.frequency_counts[j].start_epoch, y:rawData.frequency_counts[j].tweets  } );

    }

    tweetArrayObjsContainer.push( tweetArrayObj );

});

for ( var i= 0,length = tweetArrayObjsContainer.length; i < length; i++ ) {

    chartObj.chartData.push( tweetArrayObjsContainer[ i ] );

}

chartDirective = ScopeFactory.getScope('chart');
chartDirective.nvd3.drawChart(chartObj.chartData);

});

【讨论】:

  • 在这种情况下,您应该删除延迟承诺
  • 啊,太棒了!现在试试这个
  • 这并不能解决问题。他想在 所有 承诺都返回之后运行 一个 代码块。
  • 很容易更改代码以适应它,但以防万一我也添加了解决方案
  • 仍在处理中,函数formatTagData 中的代码必须运行3 次。我已经把它移到了promise.then( function( data ) {
【解决方案3】:

将每一个promise放在一个数组中然后执行:

$q.all(arrayOfPromises).then(function(){
  // this runs when every promise is resolved.
});

【讨论】:

    【解决方案4】:

    return deferred.promise; 应该是你的函数的返回值,而不是GetTweetVolFactory.returnTweetVol(),因为那是你想要承诺的。

    您的问题是您正在调用多个GetTweetVolFactory.returnTweetVol(),然后您需要合并所有这些异步调用以解决您的承诺。为此,您应该只承诺一个 GetTweetVolFactory.returnTweetVol() 呼叫:

    function promisifiedTweetVol(rawTagData, urlStuff) {
        var deferred = $q.defer(); // setting the defer
    
        GetTweetVolFactory.returnTweetVol(urlStuff)
            .success(function(data, status, headers, config) {
                rawTagData.push(data);
    
                // One the last loop, call formatTagData
                // which fills the tweetArrayObjsContainer Array
                if (loopStep === (rawTagData.length - 1)) {
                    formatTagData(rawTagData);
                    deferred.resolve();
                }
            });
    
        return deferred.promise;
    }
    

    然后在循环中调用每个promise,并返回所有promise完成后解析的promise:

    function getTagQuotes(tags) {
        var url      = 'app/api/social/twitter/volume/';
        var promises = [];
    
        // My for loop, which only returns ONCE, even if there are 3 tags
        for (var i=0; i<tags.length; i++) {
            var loopStep = if;
            rawTagData   = [];
    
            promises.push( promisifiedTweetVol(rawTagData, url+tags[i].term_id) );
        }
    
        // ...
    
        return $.when(promises);
    }
    

    您的代码还有一些问题,但您应该能够使用我的提示来解决此问题。

    【讨论】:

    • 感谢您的回答,看起来和 Ben 的类似。我在使用另一种解决方案时遇到了问题,稍后会在这里尝试您的两种解决方案。
    【解决方案5】:

    三个问题:

    1. 您没有从 getTagQuotes 方法返回延迟承诺。
    2. 您正在查看 i 以查看您是否通过了循环,并且在第一次成功调用之前 for 循环已经完成 (i == (tags.length - 1))。
    3. 您在循环的第一次迭代中调用了return,因此您甚至没有到达第二项。

    这是更正的代码(尚未测试)

    function getTagQuotes(tags) {
        var deferred = $q.defer(); // setting the defer
        var url      = 'app/api/social/twitter/volume/';
        var tagsComplete = 0;
    
        for (var i=0; i<tags.length; i++) {
            rawTagData   = [];
            GetTweetVolFactory.returnTweetVol(url+tags[i].term_id)
                .success(function(data, status, headers, config) {
                    rawTagData.push(data);
                    tagsComplete++;
    
                    if (tagsComplete === tags.length) {
                        formatTagData(rawTagData);
                        deferred.resolve();
                    }
                });
        }
    
        return deferred.promise;
    }
    

    【讨论】:

    • 是的,这就是解决方案! :D 我的tweetArrayObjsContainer 正在被填充,然后在承诺之后,它被推入chartObj.chartData 然后我可以绘制我的图表。
    【解决方案6】:

    使用延迟被广泛认为是一种反模式。如果您的 Promise 库支持 Promise 构造函数,那么这是创建您自己的 Promise 的更简单方法。

    我通常使用具有all 函数的promise 实现,而不是试图在一个中解决所有promise。然后我创建一个函数返回一个事物的承诺,然后创建另一个函数返回所有事物的承诺。

    使用map() 函数通常也比使用for 循环更简洁。

    这是一个通用食谱。假设你的 Promise 实现有点像 all 函数:

    var fetchOne = function(oneData){
     //Use a library that returns a promise
     return ajax.get("http://someurl.com/" + oneData);
    };
    
    var fetchAll = function(allData){
      //map the data onto the promise-returning function to get an
      //array of promises. You could also use `_.map` if you're a 
      //lodash or underscore user.
      var allPromises = myData.map(fetchOne);
      return Promise.all(allPromises);
    };
    
    var allData = ["a", "b", "c"];
    var promiseForAll = fetchAll(allData);
    
    //Handle the results for all of the promises.
    promiseForAll.then(function(results){
      console.log("All done.", results);
    });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-03-25
      • 2018-10-23
      • 2011-08-16
      • 2013-04-24
      • 1970-01-01
      • 2014-07-24
      • 1970-01-01
      相关资源
      最近更新 更多