El nuevo objeto jQuery $.Callbacks()

22 Nov 2011

Introducción

En el post anterior revisamos los nuevos métodos on() y off() de la versión 1.7 de jQuery, sin duda, una de las innovaciones más interesantes.

Siguiendo con el estudio de esta versión, nos toca ahora echarle un vistazo a otra importante novedad: el objeto $.Callbacks().

Básicamente, $.Callbacks ofrece un completo stack (conjunto de elementos) donde definir una serie de funciones que serán ejecutadas en secuencia como resultado de una determinada condición o acción previa.

Como la explicación puede resultar confusa, comencemos con un ejemplo para ir luego avanzando con cada uno de los métodos que ofrece este nuevo elemento:

Ejemplo de $.Callbacks

Tenemos dos funciones como las siguientes:

var fn1 = function( myStr ){
  console.log( 'Fn1 says: ', myStr );
}
 
var fn2 = function( myStr ){
  console.log( 'Fn2 says ', myStr );
}

Bien; nada complejo: dos funciones simples que reciben un argumento para pintarlo en la consola. Ahora, asociemos ambas funciones al nuevo objeto $.Callbacks para crear una pila o cola de ejecución con ellas:

var stack = $.Callbacks();
stack.add( fn1 );
stack.fire( 'Hello World' ); // Fn1 says: Hello World
 
stack.add( fn2 );
stack.fire( 'Goodbye Lenin' ); // Fn1 says: Goodbye Lenin, Fn2 says: Goodbye Lenin

Como podemos comprobar, hemos utilizado dos métodos que prácticamente hablan por si mismos: add() y fire(). El primero, se encarga de añadir una función al stack o pila mientras que el segundo, fire(), se encarga de ejecutar dicha pila a la vez que permite que enviemos argumentos a la misma.

Junto a estos dos métodos, contamos con un tercero también básico: remove(), el cual permite, como su nombre indica, borrar una referencia determinada de nuestra lista. Su uso es igual de simple:

var double = function( myVal ){
  if( $.isNumeric( myVal ) ){
    console.log( myVal * 2 );
  }else{
    console.log( myVal, ' is not Numeric!' );
    return false;
  }
}
var triple = function( myVal ){
  if( $.isNumeric( myVal ) ){
    console.log( myVal * 3 );
  }else{
    console.log( myVal, ' is not Numeric!' );
    return false;
  }
}
 
var stack = $.Callbacks();
stack.add( double );
stack.fire( 5 ); // 10
stack.fire( 'foo' ); // foo is not Numeric!
 
stack.add( triple );
stack.fire( 10 ); // 20, 30
stack.fire( 'bar' ); // bar is not Numeric!
 
stack.remove( double );
stack.fire( 2 ); // 6

Lo primero es comentar el uso de otra de las nuevas funcionalidades de jQuery 1.7: la función isNumeric(). Con ella comprobamos si un determinado valor se corresponde con un valor numérico, independientemente de que se le pase como entero o cadena:

console.log( $.isNumeric(5) ); // true
console.log( $.isNumeric('10') ); // true
console.log( $.isNumeric('one') ); // false
console.log( $.isNumeric(NaN) ); // false

NOTA: la última comprobación puede resultar confusa ya que para Javascript, NaN se corresponde con un valor númerico. jQuery, sin embargo, considera que NaN no se corresponde con un número.

Volviendo al ejemplo anterior, vemos como remove() suprime una determinada función del stack. Pero si en lugar de una concreta, quisiéramos eliminar todas las funciones que hayamos incluído en nuestra pila, contamos con el método empty() para vaciarla de forma inmediata:

stack.empty();
console.log( stack.has( triple ) ); // false

NOTA: El método has() lo revisaremos más adelante en este mismo artículo.

Separando pilas

Dado que, como hemos visto en los ejemplos anteriores, las pilas se asocian a variables, podemos tener varios entornos de forma simultánea.

var calcDouble = $.Callbacks();
var calcTriple = $.Callbacks();
 
calcDouble.add( double );
calcTriple.add( triple );
 
calcDouble.fire( 10 ); // 20
calcTriple.fire( 20 ); // 60

Con esto, podemos tener varias colas o listas de ejecución independientes.

Otros métodos

Además de add, fire y remove, $.Callbacks soporta otros métodos que pueden resultar muy interesantes:

fireWith(): con este método, se puede especificar el contexto y los argumentos que se pasamos a la pila:

var sum = function(){
  var result = 0;
  for( var x=0, i=arguments.length; x < i; x++){
    result += arguments[x];
  }
 
  console.log( result );
}
 
var stack = $.Callbacks();
stack.add( sum );
 
stack.fireWith( window, [2, 5, 10, 3] ); // 20

En el ejemplo anterior, hemos creado una función sum que devuelve la suma de todos aquellos valores que se le pasan como argumentos. Para ejecutarla desde nuestra pila, hemos utilizado el fireWith pasándole como contexto window y como argumentos un array con diversos valores.

Tenemos que tener en cuenta aquí que lo que recibe la función sum no es el array, sino que cada uno de los valores que hemos indicado se correspondería con un argumento en nuestra función:

var sum = function(/* arg1, arg2, arg3, ... */){
  //...
}

has(): este método sirve para determinar si una función concreta forma parte de nuestra pila.

var fn1 = function(){
  console.log( 'Hello World' );
};
var fn2 = function(){
  console.log( 'Goodbye Lenin' );
};
 
var stack = $.Callbacks();
stack.add( fn1 );
 
console.log( stack.has( fn1 ) ); // true
console.log( stack.has( fn2 ) ); // false

fired(): este método determina si una determinada lista ha sido ejecutada al menos una vez:

var foo = function( value ){
  console.log( 'foo:' + value );
}
 
var stack = $.Callbacks();
stack.add( foo );
 
stack.fire( 'hello' ); // foo: hello
stack.fire( 'world '); // foo: world
 
console.log( stack.fired() ); // true

disable(): este método inutiliza la lista e impide la ejecución de la misma en futuras llamadas:

var foo = function( value ){
  console.log( value );
}
 
var stack = $.Callbacks();
stack.add( foo );
 
stack.fire( 'foo' ); // foo
 
stack.disable();
 
stack.fire( 'foobar' ); // foobar isn't output so nothing is printed

lock() y locked(): el primer método bloquea la lista para impedir que pueda ser modificada de forma dinámica desde el interior de la misma. El segundo método comprueba si la lista ha sido bloqueada devolviendo un valor booleano.

Indicadores

El objeto $.Callbacks() soporta además indicadores o flags que determinan el comportamiento de la lista. Estos indicadores se especifican al iniciar el objeto y se corresponden con una serie de palabras reservadas separadas por comas. Los posibles valores son:

  • once: especifica que la lista solo se ejecutará una vez (igual que Deferred).
  • memory: al igual que en Deferred, los valores previos se memorizan por lo que a cada llamada de la lista, éstos se tienen en cuenta.
  • unique: asegura que las funciones que forman una lista, solo se pueden incluir una vez evitando duplicados.
  • stopOnFalse: interrumpe la pila de ejecución cuando alguna de las funciones devuelve un valor false.

Algunos ejemplos:

var fn1 = function( str ){
  console.log( 'Fn1 says: ' + str );
};
 
var fn2 = function( str ){
  console.log( 'Fn2 says: ' + str );
};
 
var callbacks = $.Callbacks( "memory" );
 
callbacks.add( fn1 );
callbacks.fire( "foo" );
 
callbacks.add( fn2 );
callbacks.fire( "bar" );
 
// Fn1 says: foo
// Fn2 says: foo
// Fn1 says: bar
// Fn2 says: bar

En el caso anterior, aunque fn2 no estaba incluida en la lista antes de la primera ejecución (fire), al si estarlo más adelante en el código, funciona devolviendo el valor ‘memorizado’ en fn1.

var fn1 = function(){
  console.log( 'Hello World' );
}
 
var stack = $.Callbacks( 'unique' );
stack.add( fn1 );
stack.fire(); // Hello World
 
stack.add( fn1 ); // repeat addition
stack.fire(); // Hello World --> only once

Aunque hemos incluido un par de veces la función fn1, gracias al indicador unique ésta solo se ejecuta una vez.

var fn1 = function(){
  console.log( 'Hello World' );
  return false;
};
 
var fn2 = function(){
  console.log( 'Goodbye Lenin' );
};
 
var stack = $.Callbacks( 'stopOnFalse' );
stack.add( fn1, fn2 );
stack.fire(); // Hello World

En esta ocasión, aunque dentro de nuestra lista estarían definidas las funciones fn1 y fn2, al devolver la primera un false y habiendo especificado el indicador stopOnFalse, la ejecución se interrumpe inmediatamente.

Para definir más de un indicador al mismo tiempo, solo tenemos que separarlos con espacios:

var stack = $.Callbacks( 'unique stopOnFalse' );

Conclusión

Pues esto es todo; un nuevo objeto jQuery con el que contar en nuestro inventario para programar callbacks de forma precisa.

Más:

{5} Comentarios.

  1. ashrey

    Has colocado .FireWidth en vez de FireWith

    • Carlos Benítez

      Cierto! Una errata.

      Ya está corregido; muchas gracias!

  2. dakotadelnorte

    Buenas, yo tengo dos funciones:

    comprobar(param1, param2); y guardar();

    necesito que la ejecución sea secuencial, es decir, primero comprobar y luego guardar, pero la función de comprobar hace una llamada asíncrona al servidor y establece en una variable global un valor que luego es usado en guardar.

    Estoy haciendolo como sigue:
    var callbacks = $.Callbacks();
    callbacks.add(comprobar);
    callbacks.fire(valor1, valor2);

    guardar();

    Y no me funciona, es que no he comprendido bien como se usa callbacks? Porque en otra parte si que he hecho uso de callbacks, me funciona bien, y lo he usado como sigue:

    function funcion1(valor){
    //Petición asincrona al servidor
    }

    var callbacks = $.Callbacks();
    callbacks.add(funcion1);
    callbacks.add(funcion1);

    callbacks.fire(valor1);

    Y de esta forma si que me lo hace bien, es decir, “emulando” una pila de llamadas síncronas.

    Agradecería vuestra ayuda.

    Saludos,

    P.D.: Por cierto, nunca había “usado” vuestro blog, pero si que hago un uso intensivo de vuestra biblioteca! Enhorabuena porque está muy lograda, en funcionalidad y contenido.

  3. Jonathan Javier

    $.callbacks() me parece un poco similar a Backbone.events.trigger(), aunque no se cual salió primero, aun así la versión de jQuery es mucho mas compleja. Muy interesante, por cierto.

  4. Luis

    Cuando mencionas “Stack” deberías mencionar “Fila o Cola”. Me supongo que conoces las diferencias.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *