Search code examples
javascriptfirefoxfirefox-addonfirefox-addon-restartless

Caching functions in an Array


In a discussion at Passing 'this' and argument to addEventListener function without using bind, caching functions was mentioned.

For example, considering the following Event Listeners:

window.addEventListener('event1', callback1, false);
window.addEventListener('event2', callback2, false);
window.addEventListener('event3', callback3, false);
window.addEventListener('event4', callback4, false);
window.addEventListener('event5', callback5, false);

Can their removal be cached (for example in an array)?

var unloaders = []; // Keeps track of unloader functions
unloaders.push(window.removeEventListener('event1', callback1, false));
unloaders.push(window.removeEventListener('event2', callback2, false));
unloaders.push(window.removeEventListener('event3', callback3, false));
unloaders.push(window.removeEventListener('event4', callback4, false));
unloaders.push(window.removeEventListener('event5', callback5, false));

Finally, if they can be cached, how can they be executed at the correct time?

for (let i = 0, len = unloaders.length; i < len; i++) {
  //
}

Solution

  • unloaders.push(window.removeEventListener('event1', callback1, false)) will not put the function in the array to be executed later, but execute the function and put the result value into the array, i.e. not what you want.

    The unload from the other question will actually construct an anonymous closure function and put it into the array, so simplified:

    var unloaders = []; // Keeps track of unloader functions
    unloaders.push(function() {
      window.removeEventListener('event2', callback2, false);
    });
    

    This is somewhat analog to binding the function and putting the bound function into the array, so the following would yield the same result:

    // This just binds the function, effectively creating a new function,
    // but does not execute it (yet)
    var bound = window.removeEventListener.bind(window, 'event2', callback2, false);
    unloaders.push(bound);
    

    I like the first style better, but both are OK and actually not having a closure but a bound function might avoid some issues in some circumstances where the closure closes over too much stuff keeping it artificially alive. But that is usually a rare occurrence.

    Anyway to finally call the functions stored in the array, you'd just have to iterate over it and then call the functions one after another.

    for (let i = 0, len = unloaders.length; i < len; i++) {
      unloaders[i]();
    }
    

    But, to avoid that an exceptions exit the loop early, I suggest you wrap the calls in a try-catch.

    for (let i = 0, len = unloaders.length; i < len; i++) {
      try {
        unloaders[i]();
      }
      catch (ex) {
        // Do something
      }
    }
    

    Actually, it might be preferable to call unloaders in the reverse order (last-in, first-out).

    for (let i = unloaders.length - 1; i >= 0; i--) {
      try {
        unloaders[i]();
      }
      catch (ex) {
        // Do something
      }
    }
    

    The unload function from the other question has some more magic in it: It returns a function that lets you call the unloader you just registered at any time, properly removing it from the unloaders array when doing so. This is important for the unloadWindow function I also provided.