How to execute a function after two or more asynchronous events?

11

I have a jQuery script that starts two or more asynchronous operations at the same time, and I would like to execute a callback when all are complete. An example would be to start an animation to hide the current page, while I make an Ajax request to load the contents of the next page. Both the animation can finish first, as well as the request. After both are finished, I would like to display the new page.

I do not want the new page to be displayed before the previous page was fully hidden , even if it was before your content is ready . So the final callback should not be in either event.

How to do it? Preferably, I'm looking for a generic solution that works with two or more concurrent asynchronous events (it's rare, but I could for example have to do more than one concurrent Ajax request, and just run the callback after have all the results).

Note: I asked the question for JavaScript in general, but a specific jQuery solution (for example, something involving Deferred s or Promise s) would also be good in size. / p>     

asked by anonymous 20.01.2014 / 16:30

3 answers

8

As you mentioned, you can use jQuery % s of% s (something like "deferred") and Deferred s (promises).

You call all actions that you want to occur concurrently within a Promise :

$.when(acao1, acao2)

Note that these actions should return promises so they can be used in deferred .

$.when(...) , then returns a when for all those promises. It's like an object to check all the promises in the future. Then you can call Deferred (or even other functions of then , such as Deferred and done ) to what you want to happen after all promises are fulfilled or at least one promise is not fulfilled

$.when(acao1, acao2).then(
  function(resultadoAcao1, resultadoAcao2) {
    // roda depois de acao1 e acao2 terminarem
  }, 
  function(resultadoAcaoErro) {
    // a primeira ação com erro termina aqui com o resultado do erro
  }
});

On success, action results are passed to the first callback of fail in the order that the actions were called. If any action results in an error, the second callback is called with this error. Other actions are ignored in this case. (See example here ).

Your example looks like this (without error handling):

$.when(
    // Ação 1: esconder a página
    $(".pagina:eq(0)").hide(Math.random()*4000).promise(),
    // Ação 2: carregar a nova
    $.getJSON('http://services.odata.org/OData/OData.svc/Products?$format=json'))
.then(function (hidden, jsonRequest) {
    $(".pagina:eq(1)")
        .find("strong")
        .text(jsonRequest[0].value[0].Name)
    $(".pagina:eq(1)").show(2000);
});

Note that I changed then to search for a JSON on the internet, which I call getJSON to use promise as a deferred to your animation ( hide already returns a promise).

    
21.01.2014 / 13:38
6

The fact that browsers used a single thread for the page's JavaScript code makes it a lot easier, since there is a guarantee that a function invoked executes from start to finish before another concurrent function does (ie there is no concurrency problem, each event handler as a whole can be considered atomic). Thus, it is simple to implement a semaphore, which accumulates the results of the individual callbacks and only calls the final callback after all individual calls are complete:

/* Recebe o número de operações assíncronas, e o callback a ser executado no final */
function semaforo(numero, callback) {
    var array = []; // Acumula os resultados de cada evento assíncrono individual

    /* Retorna um proxy a ser usado no lugar do event handler 
       Os argumentos são o índice daquele evento em particular, e o handler verdadeiro
    */
    return function proxy(indice, fn) {
        if ( !fn ) fn = function() { };
        return function handler() {
            array[indice] = fn.apply(this, arguments); // Chama o handler e guarda o result.
            if ( --numero == 0 )             // Se todos os eventos terminaram,
                callback.apply(this, array); // chama o callback com os resultados
        }
    }
}

Example usage:

var proxy = semaforo(2, function() { $("#novaTela").show(2000); });

$("#telaAntiga").hide(2000, proxy(0)); // Primeiro evento assíncrono

$.getJSON(url, data, proxy(1, function(json) { return json; })); // Segundo evento

Example in jsFiddle .

    
20.01.2014 / 16:30
2

Complementing the Jordan example, you could also use the jQuery.Deferred object in addition to doing some tests on the $ .when to make sure everything was executed correctly and then yes to resolve that Deferred and execute the code. The operation is similar, but it is interesting to separate the concepts.

ready = $.Deferred();

$.when(animacao, json)
  .done(animacaoResposta, jsonResposta, function() {
    if(jsonReposta[0].error) {
      console.log(jsonReposta[0].error);
    }
    else {
      ready.resolve();
    }
  })
  .fail(function() {
      console.log('Alguma coisa deu errado');
  });

ready.done(function () {
    // executa seu código com certeza que tudo deu certo
});

Example: link

    
30.01.2014 / 04:37