2015-10-07 15 views
6

Mam problem z moim obietnicą zwrotu kodu, mam funkcję getTagQuotes, która zawiera pętlę for, która może wywoływać wiele wywołań API w celu zwrócenia danych do tablicy.Jak przywrócić pojedynczą obietnicę po pętli for (która tworzy obietnicę w każdej iteracji) jest zakończona?

Jak mój kod ten zaczyna się poniżej:

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

Moja getTagQuotes funkcja ze zwrotem Obiecujemy:

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

zwracać uwagę tej linii

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

to w moim dla loop:

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

Wszystko działa wspaniale, jeśli tylko raz wykonam pętlę. Jednak, gdy tylko pojawi się inny znacznik (do 3), nadal zwraca tylko pierwszą pętlę/dane. Nie czeka, aż pętla for zostanie wykonana. Następnie zwróć obietnicę. Mój tweetArrayObjsContainer zawsze ma tylko pierwszy znacznik.

+0

Opcja 'return' wewnątrz pętli for zwrotów z całego' funkcji getTagQuotes'. 'return' zwróci z bieżącej funkcji, nie ma znaczenia, czy jesteś w pętli lub pętli while lub cokolwiek innego. –

+0

Jestem grany z tym, jeśli usuwam zwrot z 'getTagQuotes', wtedy otrzymuję błąd' .then funkcji undefined' na 'getTagQuotes (tags) .then (function()' –

+0

Oczywiście, ponieważ nie zwróciłeś nic Wygląda na to, że masz 3 przedmioty, z których wszystkie zwracają obietnice i chcesz poczekać na wypełnienie wszystkich obietnic przed podjęciem działania –

Odpowiedz

2

trzy kwestie:

  1. nie powrócili odroczonego obietnicę od sposobu getTagQuotes.
  2. szukałeś i, aby sprawdzić, czy przeszedłeś pętlę, a pętla for jest już zakończona (i == (tags.length - 1)) zanim pierwszy sukces zostanie wywołany.
  3. zadzwoniłeś pod numer return w pierwszej iteracji pętli, aby nie dostać się do drugiej pozycji.

Tutaj jest korygowane kod (nie testowałem go jeszcze)

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

Dzięki Ben! Moja ostatnia tabela Array szuka co Racja, robi trochę więcej testów ... –

+0

Tak, to jest rozwiązanie! : D moje 'tweetArrayObjsContainer' jest wypełniane, a po złożeniu obietnicy, jest wepchnięte w' chartObj.chartData', a następnie mogę narysować mój wykres. –

1

Należy zwrócić tablicę obietnic tutaj, co oznacza, że ​​należy zmienić getTagsQuotes jak to:

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

I następnie pętli to obiecuje tak:

if (tags.length > 0) { 

    var promises = getTagQuotes(tags); 

    promises.map(function(promise) { 

     promise.then(function(data) { 

      //Manipulate data here 

     }); 

    }); 
} 

EDIT: Jeśli chcesz, aby wszystkie obietnice zostały zakończone zgodnie z opisem w komentarzu, wykonaj następujące czynności:

if (tags.length > 0) { 

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

     //Manipulate data here 

    }); 
} 

Edit: Pełne manipulacji danymi:

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

}); 
+0

W takim przypadku powinieneś usunąć odroczona obietnica – masimplo

+0

tak było na drodze edycji – guramidev

+0

Ah genialny! Próbuję tego teraz –

0

Korzystanie deferreds jest powszechnie uważany za anty-wzór. Jeśli twoja biblioteka obietnic wspiera obiecującego kontrybutora, jest to łatwiejszy sposób na tworzenie własnych obietnic.

Zamiast próbować rozwiązać wszystkie obietnice w jednym, zwykle używam implementacji obietnicy, która ma funkcję all. Następnie tworzę jedną funkcję, która zwraca obietnicę danej rzeczy, a następnie inną funkcję, która zwraca obietnicę za wszystkie rzeczy.

Korzystanie z funkcji map() jest zwykle o wiele czystsze niż użycie pętli for.

Oto ogólny przepis. Zakładając realizację obietnicy ma jakiś smak funkcji 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); 
}); 
2

return deferred.promise; powinna być zwracana wartość swojej funkcji, a nie GetTweetVolFactory.returnTweetVol(), bo to, co zamierza promisify.

Twój problem polega na tym, że dzwonisz pod numer GetTweetVolFactory.returnTweetVol(), a następnie musisz scalić wszystkie te połączenia asynchroniczne, aby rozwiązać obietnicę. W tym celu należy promisify tylko jeden GetTweetVolFactory.returnTweetVol() połączenia:

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

a następnie wywołać każdą obietnicę w pętli i powrót obietnicę, że rozwiązuje gdy wszystkie obietnice są zakończone:

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

Istnieje kilka innych problemów z kodem, ale powinieneś być w stanie to zrobić z moją wskazówką.

+0

Dzięki za odpowiedź, wygląda na podobną do Bena. Mam kłopot z innym rozwiązaniem, spróbuję obu tutaj. –

1

Umieścić każdą obietnicę w tablicy wtedy zrobić:

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

W odniesieniu do tej kwestii i earlier question:

  • kod w ogóle będzie czystsze partia array.map() zamiast for pętle w kilku miejscach.
  • getTagQuotes() zostanie oczyszczony, budując szereg obietnic, przesyłając go do $q.all() i zwracając łączną obietnicę.
  • , a jego relacja z wywołującym zostanie oczyszczona przez zwrócenie przekształconego rawData.

Z kilku założeniach, kod powinien uprościć do czegoś takiego:

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, 
        }; 
       }) 
      }; 
     }); 
    } 
} 
+0

Interesujące podejście, po prostu wypróbowałem to .. ale wygląda na to, że będę musiał dodać kolejny łańcuch obietnicy wewnątrz mojej funkcji 'returnTweetVol', jak również teraz. –

+0

To brzmi jak (część?) Dobry pomysł. Idealnie, 'returnTweetVol()' zwróci obietnicę taką, że możesz napisać 'GetTweetVolFactory.returnTweetVol (url + tag.term_id) .then (...)', unikając potrzeby promisify on-the-fly, jak w moim odpowiedź. –

Powiązane problemy