una - trabajar con promesas javascript




¿Cómo se devuelve la promesa única después de for loop(que produce una promesa en cada iteración)? (4)

Tengo un problema con mi código de retorno prometedor, tengo una función getTagQuotes que contiene un ciclo for que puede convertir múltiples llamadas en una API para devolver datos a una matriz.

Cómo comienza mi código para esto a continuación:

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

Mi función getTagQuotes con la promesa de devolución:

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

Tome nota de esta línea

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

está dentro de mi ciclo for:

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

Todo funciona bien si solo tengo que pasar una vez. Sin embargo, tan pronto como haya otra etiqueta (hasta 3), solo devuelve el primer ciclo / datos. No espera hasta que se complete el ciclo for. Entonces devuelve la promesa. Entonces mi tweetArrayObjsContainer siempre solo tiene la primera etiqueta.


Debería devolver una serie de promesas aquí, lo que significa que debe cambiar getTagsQuotes de esta manera:

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

Y luego recorra estas promesas como esta:

if (tags.length > 0) {

    var promises = getTagQuotes(tags);

    promises.map( function( promise ) {

         promise.then( function( data ) { 

            //Manipulate data here

         });

    });
}

Editar: en caso de que desee que todas las promesas finalicen como se describe en el comentario, debe hacer esto:

if (tags.length > 0) {

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

        //Manipulate data here

    });
}

Editar : manipulación de datos completa:

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

});

El uso de diferidos es ampliamente considerado como un antipatrón. Si la biblioteca de su promesa es compatible con un instructor de promesa, esa es una manera más fácil de crear sus propias promesas.

En lugar de tratar de resolver todas las promesas en una, usualmente uso una implementación prometedora que tiene una función de all . Luego creo una función que devuelve una promesa para una cosa, y luego otra función que devuelve una promesa para todas las cosas.

Usar una función de map() también suele ser mucho más limpio que usar un bucle for .

Aquí hay una receta genérica. Asumiendo que su implementación prometedora tiene cierto sabor de una función de 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);
});

Tres problemas:

  1. no devolvió la promesa diferida del método getTagQuotes .
  2. usted estaba mirando i para ver si estaba en el ciclo, y el ciclo for ya está completo ( i == (tags.length - 1) ) antes de que el primer éxito sea llamado.
  3. invocaste return en la primera iteración del ciclo, de modo que ni siquiera llegaste al segundo elemento.

Aquí está el código corregido (aún no lo ha probado)

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; debería ser el valor de retorno de su función, no GetTweetVolFactory.returnTweetVol() , porque eso es lo que pretende promisificar.

Su problema es que está llamando a varios GetTweetVolFactory.returnTweetVol() , y luego necesita fusionar todas esas llamadas asincrónicas para resolver su promesa. Para hacer eso, debe promisificar solo una llamada 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;
}

Y luego llame a cada promesa en un ciclo y devuelva la promesa que se resuelve cuando se completen todas las promesas:

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

Hay algunos problemas más con tu código, pero deberías poder hacer que funcione con mi consejo.