Is it possible to know when all the event handlers are finished?

5

I want to know when all event handlers in an event in JavaScript are finished. Is that possible?

Example:

<button id='button' ...>Submit</button>
<script>
   ultimo = notify() {
       alert("Último!");
   };
   $( '#button' ).on('click', function() { alert('Primero') });
   $( '#button' ).on('click', function() { alert('Segundo') });
   $( '#button' ).on('click', ultimo );
</script>

With this code, I will receive 3 alerts: First , Second and Last .

But the last one may not be the last if there is code elsewhere that adds other drivers.

So, is there a way to know when all the controllers of an event end?

To be more specific, I am writing a library, and I want to do something in an event, but only after all the drivers that can be added from the users of my library.

    
asked by Flimzy 03.01.2016 в 14:55
source

3 answers

1

There is a possible option based on this site question in English (and specifically in the responses of dowski and Avram Lavinsky ), using custom events, the metadata that jQuery provides with $._data() , and knowing that the events are executed in the order in which they were associated with the element.

The idea (which would only work with the events associated with jQuery) would be as follows:

  • Create a custom event (let's name it afterclick ) and add in it all the code that you want to run when you finish the clicks.
  • BEFORE that no event is associated with the element, associate your own click detector that:

  • Prevent the propagation of the event immediately (not even the following clicks). This can be done with stopImmediatePropagation() .
  • Execute all the functions associated with the click event in order (you can get an array with those functions using $._data(this, "events").click ).
  • Request the execution of the custom event.

At first glance it seems simple, right? And the code would not be very complex either:

// asociamos un event listener con el elemento
// MUY IMPORTANTE: esto debe ejecutarse antes de que cualquier otro event listener se añada
$(elemento).on("click", function(e) {
  if (e) {
    // prevenimos la propagación del resto de eventos click
    e.stopImmediatePropagation();

    // obtenemos una lista con las acciones asociadas al evento click
    var lectoresEvento = $._data(this, "events").click;

    // para cada elemento de la lista
    for (var x = 0; x < aClickListeners.length; x++) {
      // ejecutar la función
      aClickListeners[x].handler();
    }

    // invocar el evento personalizado afterclick
    $(this).trigger("afterclick")
  }
});

The idea would be to run that code in your library, after loading jQuery and before loading any other code. Later you can associate (or not) the event afterclick to the different elements.

The custom event will always run at the end of all clicks; and if there is none, it will be executed anyway (something like the finally in the exceptions).

Advantages of this method:

  • It works: P
  • You do not need to know how many event handlers have been created, nor their name. All will appear sorted in the list of $._data() .

Disadvantages of this method:

  • Only works within the jQuery universe.
  • $._data(this, "events") does not work in versions prior to 1.8, for those you would have to modify the code a bit and use element.data('events') (as explained in this question from the English site).

Here is an example (you can see the results in the console):

// Asociamos un evento click a todos los elementos
$("*").on("click", function(e) {
  if (e) {
    e.stopImmediatePropagation();
    var lectoresClick = $._data(this, "events").click;
    for (var x = 0; x < lectoresClick.length; x++) {
      lectoresClick[x].handler();
    }
    $(this).trigger("afterclick");
  }
});

// definimos las acciones que se ejecutarán después de todas las acciones del evento click
$("button").on("afterclick", function() { 
  console.log("Ejecutados todos los eventos click de " + $(this).attr("id")); 
});

// asociamos diferentes acciones al evento click de los botones
$("#Boton1").on("click", function() { console.log("Primer evento botón 1"); });
$("#Boton2").on("click", function() { console.log("Primer (y único) evento botón 2"); });
$("#Boton1").on("click", function() { console.log("Segundo evento botón 1"); });
$("#Boton1").on("click", function() { console.log("Tercer evento botón 1"); });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<button id="Boton1">Botón 1</button>
<button id="Boton2">Botón 2</button>
<button id="Boton3">Botón 3</button>
    
answered by 08.01.2016 в 19:51
0

You can use Promises :

<!DOCTYPE html>
<html>
    <head>
    </head>
    <body>
        <button id="button">Submit</button>
        <script src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
        <script type="text/javascript">
            $(document).ready(function() {
                $("#button").on("click", todo);

            });

            function todo() {
                Promise.all([primero(), segundo(), ultimo()]).then(function(values) {
                    alert("Terminado: " + values);
                });
            }

            function primero() {
                return new Promise(function(resolve, reject) {
                    console.log("Primero");
                    resolve(1);
                });

            }

            function segundo() {
                return new Promise(function(resolve, reject) {
                    setTimeout(resolve, 2000, 2);
                });
            }

            function ultimo() {
                return new Promise(function(resolve, reject) {
                    console.log("Ultimo");
                    resolve(3);
                });
            }
        </script>
    </body>
</html>

I'm using Promise.all that you expect the end or rejection of a Array of Promises .

Each of the functions that are called within the todo function is returning a promise:

return new Promise(function(resolve, reject) { ... });

The segundo function takes longer than the other two but the todo expects the completion of all of them.

    
answered by 03.01.2016 в 16:07
-1

Use Async

async.series([
  function(callback) {
    callback(null, 'uno');
  },
  function(callback) {
    callback(null, 'dos');
  },
  function(callback) {
    callback(null, 'ultimo');
  }
], function (err, result) {
  // ['uno', 'dos', 'ultimo']
});

You could put all the drivers in an array var cont = [controlardor1, controlador2...] and pass it as an argument to async.series(cont, cb)

    
answered by 03.01.2016 в 18:12