Buscar..


Ejemplos simples de uso de devolución de llamada

Las devoluciones de llamada ofrecen una forma de ampliar la funcionalidad de una función (o método) sin cambiar su código. Este enfoque se usa a menudo en módulos (bibliotecas / complementos), cuyo código no se debe cambiar.

Supongamos que hemos escrito la siguiente función, calculando la suma de una matriz de valores dada:

function foo(array) {
    var sum = 0;
    for (var i = 0; i < array.length; i++) {
        sum += array[i];
    }
    return sum;
}

Ahora supongamos que queremos hacer algo con cada valor de la matriz, por ejemplo, mostrarlo usando alert() . Podríamos hacer los cambios apropiados en el código de foo , así:

function foo(array) {
    var sum = 0;
    for (var i = 0; i < array.length; i++) {
        alert(array[i]);
        sum += array[i];
    }
    return sum;
}

Pero, ¿y si decidimos usar console.log lugar de alert() ? Obviamente, cambiar el código de foo , siempre que decidamos hacer algo más con cada valor, no es una buena idea. Es mucho mejor tener la opción de cambiar de opinión sin cambiar el código de foo . Ese es exactamente el caso de uso para devoluciones de llamada. Solo tenemos que cambiar ligeramente la firma y el cuerpo de foo :

function foo(array, callback) {
    var sum = 0;
    for (var i = 0; i < array.length; i++) {
        callback(array[i]);
        sum += array[i];
    }
    return sum;
}

Y ahora podemos cambiar el comportamiento de foo simplemente cambiando sus parámetros:

var array = [];
foo(array, alert);
foo(array, function (x) {
    console.log(x);
});

Ejemplos con funciones asíncronas

En jQuery, el $.getJSON() para recuperar datos JSON es asíncrono. Por lo tanto, pasar el código en una devolución de llamada se asegura de que se llame al código después de que se recupere el JSON.

$.getJSON() :

$.getJSON( url, dataObject, successCallback );

Ejemplo de $.getJSON() :

$.getJSON("foo.json", {}, function(data) {
    // data handling code
});

Lo siguiente no funcionaría, porque probablemente se llamaría al código de manejo de datos antes de que se reciban los datos, porque la función $.getJSON toma un tiempo no especificado y no $.getJSON la pila de llamadas mientras espera el JSON.

$.getJSON("foo.json", {});
// data handling code

Otro ejemplo de una función asíncrona es la función animate() jQuery. Debido a que se tarda un tiempo específico para ejecutar la animación, a veces es conveniente ejecutar algún código directamente después de la animación.

.animate() :

jQueryElement.animate( properties, duration, callback );

Por ejemplo, para crear una animación de desvanecimiento después de la cual el elemento desaparece por completo, se puede ejecutar el siguiente código. Tenga en cuenta el uso de la devolución de llamada.

elem.animate( { opacity: 0 }, 5000, function() {
    elem.hide();
} );

Esto permite que el elemento se oculte justo después de que la función haya finalizado su ejecución. Esto difiere de:

elem.animate( { opacity: 0 }, 5000 );
elem.hide();

porque la última no espera a que se complete animate() (una función asíncrona), y por lo tanto el elemento se oculta de inmediato, lo que produce un efecto no deseado.

¿Qué es una devolución de llamada?

Esta es una llamada de función normal:

console.log("Hello World!");

Cuando llama a una función normal, hace su trabajo y luego devuelve el control a la persona que llama.

Sin embargo, a veces una función necesita devolver el control a la persona que llama para hacer su trabajo:

[1,2,3].map(function double(x) {
    return 2 * x;
});

En el ejemplo anterior, la función double es una devolución de llamada para el map funciones porque:

  1. La función double se asigna al map funciones por el llamante.
  2. El map funciones necesita llamar a la función double cero o más veces para hacer su trabajo.

Por lo tanto, el map funciones esencialmente devuelve el control a la persona que llama cada vez que llama a la función double . Por lo tanto, el nombre de "devolución de llamada".


Las funciones pueden aceptar más de una devolución de llamada:

promise.then(function onFulfilled(value) {
    console.log("Fulfilled with value " + value);
}, function onRejected(reason) {
    console.log("Rejected with reason " + reason);
});

Aquí la función then acepta dos funciones de devolución de llamada, onFulfilled y onRejected . Además, solo se llama a una de estas dos funciones de devolución de llamada.

Lo que es más interesante es que la función then vuelve antes de que cualquiera de las devoluciones de llamada son llamados. Por lo tanto, se puede llamar a una función de devolución de llamada incluso después de que la función original haya regresado.

Continuación (síncrona y asíncrona)

Las devoluciones de llamada se pueden utilizar para proporcionar el código que se ejecutará después de que se haya completado un método:

/**
 * @arg {Function} then continuation callback
 */
function doSomething(then) {
  console.log('Doing something');
  then();
}

// Do something, then execute callback to log 'done'
doSomething(function () {
  console.log('Done');
});

console.log('Doing something else');

// Outputs:
//   "Doing something"
//   "Done"
//   "Doing something else"

El método doSomething() anterior se ejecuta de forma sincrónica con la devolución de llamada: los bloques de ejecución hasta que devuelve doSomething() , asegurando que la devolución de llamada se ejecute antes de que el intérprete avance.

Las devoluciones de llamada también se pueden utilizar para ejecutar código de forma asíncrona:

doSomethingAsync(then) {
  setTimeout(then, 1000);
  console.log('Doing something asynchronously');
}

doSomethingAsync(function() {
  console.log('Done');
});

console.log('Doing something else');

// Outputs:
//   "Doing something asynchronously"
//   "Doing something else"
//   "Done"

Las devoluciones de llamada then se consideran continuaciones de los métodos doSomething() . Proporcionar una devolución de llamada como la última instrucción en una función se llama una llamada de cola , que es optimizada por los intérpretes de ES2015 .

Manejo de errores y ramificación de flujo de control.

Las devoluciones de llamada se utilizan a menudo para proporcionar manejo de errores. Esta es una forma de ramificación de flujo de control, donde algunas instrucciones se ejecutan solo cuando se produce un error:

const expected = true;

function compare(actual, success, failure) {
  if (actual === expected) {
    success();
  } else {
    failure();
  }
}

function onSuccess() {
  console.log('Value was expected');
}

function onFailure() {
  console.log('Value was unexpected/exceptional');
}

compare(true, onSuccess, onFailure);
compare(false, onSuccess, onFailure);

// Outputs:
//   "Value was expected"
//   "Value was unexpected/exceptional"

La ejecución del código en compare() anterior tiene dos ramas posibles: success cuando los valores reales y esperados son los mismos, y error cuando son diferentes. Esto es especialmente útil cuando el flujo de control debe ramificarse después de alguna instrucción asíncrona:

function compareAsync(actual, success, failure) {
  setTimeout(function () {
    compare(actual, success, failure)
  }, 1000);
}

compareAsync(true, onSuccess, onFailure);
compareAsync(false, onSuccess, onFailure);
console.log('Doing something else');

// Outputs:
//   "Doing something else"
//   "Value was expected"
//   "Value was unexpected/exceptional"

Debe tenerse en cuenta que las devoluciones de llamada múltiples no tienen que ser mutuamente excluyentes, ya que ambos métodos podrían llamarse. De forma similar, el compare() podría escribir con devoluciones de llamada que son opcionales (usando un noop como valor predeterminado - vea el patrón de Objeto nulo ).

Callbacks y `this`

A menudo, cuando se utiliza una devolución de llamada, se desea acceder a un contexto específico.

function SomeClass(msg, elem) {
  this.msg = msg;
  elem.addEventListener('click', function() {
    console.log(this.msg);  // <= will fail because "this" is undefined
  });
}

var s = new SomeClass("hello", someElement);

Soluciones

  • Usar bind

    bind efectivamente genera una nueva función que establece this a lo que fue pasado para bind luego llama a la función original.

      function SomeClass(msg, elem) {
        this.msg = msg;
        elem.addEventListener('click', function() {
          console.log(this.msg);  
        }.bind(this));  // <=-  bind the function to `this`
      }
    
  • Usar funciones de flecha

    Funciones de dirección se unen automáticamente la corriente de this contexto.

      function SomeClass(msg, elem) {
        this.msg = msg;
        elem.addEventListener('click',() => {   // <=-  arrow function binds `this`
          console.log(this.msg);  
        });
      }
    

A menudo, le gustaría llamar a una función miembro, idealmente pasar los argumentos que se pasaron al evento a la función.

Soluciones:

  • Usar enlace

      function SomeClass(msg, elem) {
        this.msg = msg;
        elem.addEventListener('click', this.handleClick.bind(this));
      }
    
      SomeClass.prototype.handleClick = function(event) {
        console.log(event.type, this.msg);
      };
    
  • Usa las funciones de flecha y el operador de descanso.

      function SomeClass(msg, elem) {
        this.msg = msg;
        elem.addEventListener('click', (...a) => this.handleClick(...a));
      }
    
      SomeClass.prototype.handleClick = function(event) {
        console.log(event.type, this.msg);
      };
    
  • Para los oyentes de eventos DOM en particular, puede implementar la interfaz EventListener

      function SomeClass(msg, elem) {
        this.msg = msg;
        elem.addEventListener('click', this);
      }
    
      SomeClass.prototype.handleEvent = function(event) {
        var fn = this[event.type];
        if (fn) {
           fn.apply(this, arguments);
        }
      };
      
      SomeClass.prototype.click = function(event) {
        console.log(this.msg);
      };
    

Devolución de llamada utilizando la función de flecha

Usar la función de flecha como función de devolución de llamada puede reducir las líneas de código.

La sintaxis predeterminada para la función de flecha es

() => {}

Esto puede ser utilizado como devoluciones de llamada

Por ejemplo, si queremos imprimir todos los elementos en una matriz [1,2,3,4,5]

Sin función de flecha, el código se verá así.

[1,2,3,4,5].forEach(function(x){
                 console.log(x);
            }

Con la función de flecha, se puede reducir a

[1,2,3,4,5].forEach(x => console.log(x));

Aquí la función de la function(x){console.log(x)} devolución de llamada function(x){console.log(x)} se reduce a x=>console.log(x)



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow