javascript - Cum să returnezi o singură promisiune după ce bucla for (care produce o promisiune la fiecare iterație) s-a încheiat?


Am o problemă cu codul de returnare al promisiunii mele, am o funcție getTagQuotes care conține un ciclu for care poate face mai multe apeluri către o API pentru a returna date într-un tablou.

Cum începe codul meu pentru asta mai jos:

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

Funcția mea getTagQuotes cu returnarea promise-ului:

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

Acordă atenție acestei linii

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

este în interiorul buclei mele for:

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

Totul funcționează grozav dacă trebuie să parcurg doar o dată. Totuși, de îndată ce există un alt tag (până la 3) el returnează în continuare doar primul loop/date. Nu așteaptă până când for loop-ul se termină. Apoi returnează promisiunea. Deci tweetArrayObjsContainer conține întotdeauna doar primul tag.

Răspuns #1

Trei probleme:

  1. nu ai returnat promisiunea amânată din metoda getTagQuotes.
  2. te uitai la i pentru a vedea dacă ai terminat bucla, și bucla for este deja completă (i == (tags.length - 1)) înainte ca primul succes să fie apelat.
  3. ai apelat return în prima iterație a buclei, astfel încât nici măcar nu ai ajuns la al doilea element.

Iată codul corectat (nu l-am testat încă)

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

Răspunsul #2

return deferred.promise; ar trebui să fie valoarea returnată a funcției tale, nu GetTweetVolFactory.returnTweetVol(), pentru că asta intenționezi să transformi într-o promisiune.

Problema ta este că apelezi mai multe GetTweetVolFactory.returnTweetVol(), și apoi trebuie să combini toate acele apeluri asincrone pentru a rezolva promisiunea ta. Pentru a face asta, ar trebui să transformi în promisiune doar un singur apel 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;
}

Și apoi apelează fiecare promisiune într-un ciclu și returnează promisiunea care se rezolvă când toate promisiunile sunt finalizate:

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

Mai există câteva probleme cu codul tău, dar ar trebui să reușești să îl faci să funcționeze cu sfatul meu.