如何在for循环之后返回单个promise(在每次迭代时产生一个promise)是否完成?

时间:2015-10-07 16:50:27

标签: javascript angularjs for-loop promise angular-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函数带有promise:

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始终只有第一个标记。

6 个答案:

答案 0 :(得分:2)

三个问题:

  1. 您没有从getTagQuotes方法返回延期承诺。
  2. 您正在查看i以查看您是否通过循环,并且for循环已经完成(i == (tags.length - 1)),然后才能调用第一个成功。
  3. 你在循环的第一次迭代中调用return,这样你甚至没有进入第二项。
  4. 此处已更正的代码(尚未对其进行测试)

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

答案 1 :(得分:2)

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

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

答案 2 :(得分:1)

你应该在这里返回一个promises数组,这意味着你应该改变这样的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);

});

答案 3 :(得分:1)

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

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

答案 4 :(得分:0)

使用延迟被广泛认为是一种反模式。如果您的promise库支持promise contstructor,那么这就是创建自己的承诺的更简单方法。

我通常使用具有all函数的promise实现,而不是尝试在一个中解析所有promise。然后我创建一个函数,它返回一个事物的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);
});

答案 5 :(得分:0)

参考这个问题和earlier question

  • 在一些地方,代码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,
                    };
                })
            };
        });
    }
}