Entendiendo llamadas asíncronas y callbacks en Javascript

admin

March 23, 2016

javascript

No Comment

Los procesos asíncronos son difíciles de comprender cuando uno tiene una experiencia basada en programación procedural estricta u orientación a objetos, cuando estamos acostumbrados a que todas las variables y funciones tienen que estar previamente declaradas en el mismo módulo, script, archivo o projecto.

De repente utilizamos una librería basada en llamadas asíncronas, como JQuery, y nos encontramos con que debemos programar implementaciones de funciones “callback” que nosotros no declaramos, que esperan parámetros determinados (una firma particular del método) pero que, como no declaramos en ningún lado, nos confunde.

Equivalencias con OOP

En orientación a objetos, sería como escribir los métodos de implementación de una interface o programar un procedimiento delegado. El problema es que en Javascript todo son funciones y no se sigue un orden estricto en estas declaraciones, como en otros lenguajes orientados a objetos, simplemente se pasa la implementación como parámetro en una función.

$.each([ 52, 97 ], function( index, value ) {
  alert( index + ": " + value );
});

Se que puedes pensar ¡Pero es que es bastante sencillo! ¿Cómo puedes confundirte con eso?
Pues fácilmente, si esperas haber declarado previamente index y value, tomándolas como variables, siendo que son parámetros.

Esto puede causar confusión y la mejor manera de atacar el problema, es declarar todo previamente, y utilizar variables que representen las funciones, así:

// El arreglo a recorrer
var arr = [52, 97];

// La función a aplicar a cada item en el arreglo recorrido
// Index el índice del item
// Value el valor del item
var fn = function ( index, value ) {
    console.log( index + ": " + value );
};

// Mandamos llamar each, con el arreglo y la función como parámetros
$.each(
    arr,
    fn
);

Ahora, bien, lo mejor es construir tu mismo las llamadas asíncronas para ir comprendiendo mejor el asunto.

Una llamada asíncrona es una llamada a una función que se ejecuta en un proceso separado, en un “hilo” aparte del proceso principal. Se usan para evitar que el proceso principal se bloquee, en caso de que la llamada no responda.

Primero la especificación:

Queremos una función que tome dos parámetros, los procese y cuando termine realice una función personalizada con la respuesta. Es decir, estamos creando una interface que debe ser implementada por el desarrollador que use la función.

Hasta aqui todo clarísimo, pero hay que tomar en cuenta que cuando usamos algo más estricto como orientación a objetos tenemos que declarar la interfaz, heredarla y posteriormente implementarla. En Javascript la declaración de la interfaz o del delegado no es necesaria, es un lenguaje muy dinámico e inteligente. Los que estamos confundidos somos nosotros, así que vamos a declararla, aunque sea en comentarios. Otra cosa a tomar en cuenta es que solo necesitamos la firma de la función, no tiene que llamarse igual, ni los parámetros tampoco.

// La interfaz será:
// proc( res ) { // TODO };
// Implementamos:
function onResponse( response ) {
    // Simplemente lo loggeamos
    console.log(response);
}

/*
 * Hace una llamada asíncrona, para posteriormente procesar el resultado
 * param1 y param2 son los datos a procesar
 * callBack la función que se generará después del procesamiento
 */
function asyncCall( param1, param2, callBack ) {

    // Esta es la función que ejecutará el setTimeout
    var fn = function() {
        // Procesamos los parámetros
        var result = param1 + param2;
        // Mandamos llamar la callBack, que como sabemos, espera un parámetro "response", 
        // le estamos pasando "result" como parámetro
        callBack( result );
    };
  
    // Usaremos setTimeout para emular comportamiento asíncrono
    // lo mandamos llamar, pasándole la función a ejecutar y el tiempo de retraso (2 segundos)
    setTimeout(
        fn,
        2000
    );
}

// Y aqui ejecutamos, mandamos llamar asyncCall con dos parámetros de datos 
// y una función, que procesará el resultado
asyncCall ( 20, 30, onResponse );

Al ejecutar este código pasarán dos segundos, luego se imprimirá “50” en la consola. Se que estás pensando, ¡Pero que función tan inútil! Y tienes razón, pero su propósito es meramente ilustrativo, ya sabemos con que compararla en orientacion a objetos o procedural y conocemos una notación para que el código sea más comprensible, aunque más verboso, por lo menos en lo que nos acostumbramos al dinamísmo de javascript.

Un ejemplo de la vida real

En los proyectos reales el comportamiento asíncrono muchas veces son llamadas a servidor http. Vamos a ver el clásico ejemplo.

Primero, una función simple:

// Llamada sencilla a data.txt
function ajaxTest() {
  // El objeto request
  var xhttp = new XMLHttpRequest();
  
  // La función que le pasemos a onreadystatechange
  // se repetirá hasta llegar al 4 (completed)
  xhttp.onreadystatechange = function() {
    if (xhttp.readyState == 4 && xhttp.status == 200) {
     // Si completa y exitosa
     // simplemente logeamos al respuesta
     console.log(xhttp.responseText);
    }
  };
  // Método, url y asíncrona
  xhttp.open("GET", "data.txt", true);
  // Iniciamos la petición
  xhttp.send();
}

// La mandamos llamar
ajaxTest();

// Esto imprimirá el contenido del archivo en la consola.

Ahora, pasaremos callbacks a la petición, uno cuando es exitosa (una función onSuccess), y uno cuando no (función onError).

Para objeto de simplicidad, le pasaremos la respuesta en texto a onSuccess, y no le pasaremos parámetros a onError, simplemente imprimirá “Error” en la consola.

// Esta función creará peticiones ajax, recibiendo como parámetros
// la url del recurso,
// una función para procesar la respuesta ( onSuccess )
// una función en caso de respuesta no exitosa ( onError )
function ajaxAsyncTest(url, onSuccess, onError) {
  // El objeto request
  var xhttp = new XMLHttpRequest();
  
  // Función a ejecutar con cada cambio de estado
  xhttp.onreadystatechange = function() {
    // Si la petición es completada
    if (xhttp.readyState == 4 ) {
      // si la respuesta es exitosa
      if ( xhttp.status == 200 ) {
        // Mandamos llamar el callback onSuccess
        // enviandole la respuesta como parámetro
      	onSuccess(xhttp.responseText);
        // La firma para la implementación sería:
        /*
          function onSuccess ( response ) {
            // TODO
          }
        */
      } else {
        // Si no, el callback onError
      	onError();
        // La firma sería una función sin parámetros
        /*
          function onError() {
            // TODO
          }
        */
      } // end if then else status 200
    } // end if readyState 4
  }; // enf function onreadystatechange

  // configuramos método y url
  xhttp.open("GET", url, true);
  // Iniciamos la petición
  xhttp.send();
} // end function ajaxAsyncTest


// Esta función se disparará cuando al petición sea exitosa
// espera la respuesta en formato texto, en el parámetro "response"
var onSuccess = function (response) {
	console.log('Success!');
	console.log(response);
};


// Esta función se disparará en caso de error.
var onError = function () {
	console.log('Error!');
};


//  Mandamos llamar la petición ajax, pasandole la url y las funciones
//  onSuccess y onError
var url = "data.txt";
ajaxAsyncTest(url, onSuccess, onError);
// Esto imprimirá el contenido del archivo data.txt en la consola.

Como podemos observar, declarando todo en variables deja más legible el código, aunque más verboso. Esto no es realmente muy importante, puesto que podemos “minificar” nuestro script para producción, lo que reducirá bastante su tamaño y mantenerlo “desminificado” en desarrollo, lo que incrementará su legibilidad y mantenibilidad.

Ahora tenemos un mejor entendimiento de las funciones “callback” y podemos atacar librarias más complejas con más confianza, basándonos siempre, claro está, en la documentación de las mismas.

Related Posts

Safe Type Classes con Javascript Puro

Hoy en día tenemos TypeScript, un superset de Javascript type safe de Microsoft, y Dart, otro superset type safe pero de Google, y estan muy bien, pero digamos que no tienes el tiempo ni/o la voluntad de aprender otro lenguaje o superset de uno que ya sabes, y quieres implementar algo de type safety en […]

Read More

7 extensiones a las cadenas en Javascript que ahorran mucho código y se ven elegantes

admin

March 26, 2016

javascript

No Comment

Estas 7 funciones básicas de cadenas son fáciles de implementar como extensiones del objeto String, son muy utilizadas y el código resultante es mucho más legible, mantenible y elegante: 1 y 2: Encode y Decode Uri A pesar de que las uso seguido, eso de añadir el sufijo “Component” a la función siempre se me […]

Read More

Leave a Reply

Your email address will not be published. Required fields are marked *

Busca en el blog aqui

Herramientas Útiles

Suscribete al blog

Recibe en tu correo las últimas publicaciones

Publicidad

Sígueme en Twitter