如何在 for 循环(每次迭代都会产生一个承诺)完成后 return 单一承诺?

How to return single promise after for loop (which produces a promise on every iteration) is complete?

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

我的代码是如何开始的:

// 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 函数带有承诺 return:

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 个),它仍然只有 return 第一个 loop/data。它不会等到 for 循环完成。然后 return 承诺。所以我的 tweetArrayObjsContainer 总是只有第一个标签。

你应该 return 这里的承诺数组,这意味着你应该像这样更改 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;
}

然后像这样遍历这个承诺:

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);

});

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

我通常使用具有 all 函数的 promise 实现,而不是尝试解决所有 promise。然后我创建一个 returns 对某事物的承诺的函数,然后创建另一个 returns 对所有事物的承诺的函数。

使用 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);
});

三期:

  1. 您没有 return 来自 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;
}

return deferred.promise; 应该是函数的 return 值,而不是 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 和 return 在所有 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);
}

您的代码还有一些问题,但您应该能够按照我的提示进行操作。

将每个承诺放入一个数组中,然后执行:

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

参考这个问题和 :

  • 代码通常会在几个地方用 array.map() 代替 for 循环更清晰。
  • getTagQuotes() 将通过构建一系列承诺、将其提交给 $q.all() 并返回聚合承诺来变得更简洁。
  • 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,
                    };
                })
            };
        });
    }
}