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

Asked

Viewed 12,452 times

11

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

I would not like the new page to be displayed before the previous one was totally hidden, even if it were before your content is ready. So that the final callback should not be in either one or another event.

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

Note: I asked the question for Javascript, in general, but a specific solution for jQuery (for example, something involving Deferreds or Promises) would also be of good size.

3 answers

9


As you mentioned, you can use jQuery Deferreds (something like "postponed") and Promises (promises).

You call all actions you wish to occur concurrently within a $.when(...):

$.when(acao1, acao2)

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

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

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

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

Your example gets like this (error-free):

$.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 getJSON to search for a JSON on the Internet, which I call promise to use hide as a deferred for your animation (getJSON already returns a promise).

  • 3

    Great answer, actually I had already read about the when but I didn’t remember him. And that’s calling the promise on the return of hide is news to me, I thought it would be an obstacle to use Promisebut I see it’s not. +1 and I accept!

6

The fact that the browsers used a single thread for the Javascript code of the page makes it much easier, as there is a guarantee that an invoked function runs from start to finish before another concurrent function does (i.e. there is no competition issue, each Event Handler as a whole can be considered atomic). Thus, it is simple to implement a semaphore, which accumulates the results of callbacks and only calls the callback end 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 of use:

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.

3

Complementing the Jordan example, you could also use the jQuery.Deferred object as well as running a few tests on $.when to make sure everything was executed correctly and then solve this Deferred and run 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: http://jsfiddle.net/gilbarbara/xNL7J/1/

Browser other questions tagged

You are not signed in. Login or sign up in order to post.