Obtener una mejor comprensión de las funciones de devolución de llamada en JavaScript

163

Entiendo pasar una función a otra función como devolución de llamada y hacer que se ejecute, pero no entiendo la mejor implementación para hacerlo. Estoy buscando un ejemplo muy básico, como este:

var myCallBackExample = {
    myFirstFunction : function( param1, param2, callback ) {
        // Do something with param1 and param2.
        if ( arguments.length == 3 ) {
            // Execute callback function.
            // What is the "best" way to do this?
        }
    },
    mySecondFunction : function() {
        myFirstFunction( false, true, function() {
            // When this anonymous function is called, execute it.
        });
    }
};

En myFirstFunction, si devuelvo una nueva devolución de llamada (), entonces funciona y ejecuta la función anónima, pero no me parece el enfoque correcto.


fuente
¿Correcto en qué sentido? Por lo general, las devoluciones de llamada se utilizan para los controladores de eventos, especialmente las llamadas Ajax, que son asíncronas, básicamente cosas en las que no se sabe cuándo (o si) vendrá una respuesta.
cletus
2
Por cierto argumentos son como conjunto, pero no matriz, por lo que no puede hacer argument.length pero se puede convertir en una matriz utilizando el método de la rebanada ...
paul
1
@paul, aunque tienes razón en que argumentsno es una matriz, aún puedes hacer referencia a su longitud como arguments.length: pruébalo. Esta propiedad se refiere al número de argumentos realmente pasados, y no necesariamente al número de parámetros en la firma de la función.
hotshot309

Respuestas:

132

Solo puedes decir

callback();

Alternativamente, puede utilizar el callmétodo si desea ajustar el valor de thisla devolución de llamada.

callback.call( newValueForThis);

Dentro de la función thisestaría lo que newValueForThissea.

krosenvold
fuente
91

Debe verificar si la devolución de llamada existe y si es una función ejecutable:

if (callback && typeof(callback) === "function") {
    // execute the callback, passing parameters as necessary
    callback();
}

Muchas bibliotecas (jQuery, dojo, etc.) usan un patrón similar para sus funciones asincrónicas, así como node.js para todas las funciones asíncronas (nodejs generalmente pasa errory datapara la devolución de llamada). ¡Mirar en su código fuente ayudaría!

arunjitsingh
fuente
¿Por qué se convierte callbacken cadena y luego se verifica su tipo? ¿Mejorará esto el rendimiento? Esto es como verificar el tipo, verificar si el booleano convertido devuelve verdadero y luego verificar su tipo nuevamente y probarlo contra la cadena ... ¿Podría explicar por qué?
headacheCoder
Tengo curiosidad por qué necesita la primera afirmación para la devolución de llamada ... ¿es para verificar nulo o indefinido? ¿No lo typeof(callback)lograrías por ti? typeof(null) === "Object",typeof("undefined") === "undefined"
PJH
1
Cortocircuito Y. Si la devolución de llamada no existe, no se moleste en calcular su tipo. Aunque tienes razón. No es necesario con el typeof (), pero haré un jsperf y veré si vale la pena el cortocircuito.
arunjitsingh
@headacheCoder: callbackno se está convirtiendo en una cadena, se está comprobando su tipo para ver si es una función, antes de que se llame. Presumiblemente, el código acepta callbackcomo argumento, y no está seguro de que el argumento sea de un tipo invocable, o tal vez los argumentos sean de varios tipos en un intento de proporcionar una forma de polimorfismo en el que el código pueda reaccionar de manera diferente a diferentes typeofargumentos.
LeeGee
34

Hay 3 posibilidades principales para ejecutar una función:

var callback = function(x, y) {
    // "this" may be different depending how you call the function
    alert(this);
};
  1. devolución de llamada (argumento_1, argumento_2);
  2. callback.call (algún_objeto, argumento_1, argumento_2);
  3. callback.apply (some_object, [argumento_1, argumento_2]);

El método que elija depende de si:

  1. Tiene los argumentos almacenados en una matriz o como variables distintas.
  2. Desea llamar a esa función en el contexto de algún objeto. En este caso, el uso de la palabra clave "this" en esa devolución de llamada haría referencia al objeto pasado como argumento en call () o apply (). Si no desea pasar el contexto del objeto, use nulo o indefinido. En el último caso, el objeto global se usaría para "esto".

Documentos para Function.call , Function.apply

Ionuț G. Stan
fuente
6

Las devoluciones de llamada se refieren a señales y "nuevo" se trata de crear instancias de objetos.

En este caso, sería aún más apropiado ejecutar solo "callback ();" que "return new callback ()" porque de todos modos no está haciendo nada con un valor de retorno.

(Y la prueba argumentos.length == 3 es realmente torpe, mejor, mejor comprobar que el parámetro de devolución de llamada existe y es una función).

annakata
fuente
6

la implementación adecuada sería:

if( callback ) callback();

esto hace que el parámetro de devolución de llamada sea opcional.

faeb187
fuente
¿Qué pasa si el argumento de devolución de llamada no es una función?
Yaki Klein el
2

Puedes usar:

if (callback && typeof(callback) === "function") {
    callback();
}

El siguiente ejemplo es un poco más completo:

function mySandwich(param1, param2, callback) {
  alert('Started eating my sandwich.\n\nIt has: ' + param1 + ', ' + param2);
  var sandwich = {
      toppings: [param1, param2]
    },
    madeCorrectly = (typeof(param1) === "string" && typeof(param2) === "string") ? true : false;
  if (callback && typeof(callback) === "function") {
    callback.apply(sandwich, [madeCorrectly]);
  }
}

mySandwich('ham', 'cheese', function(correct) {
  if (correct) {
    alert("Finished eating my " + this.toppings[0] + " and " + this.toppings[1] + " sandwich.");
  } else {
    alert("Gross!  Why would I eat a " + this.toppings[0] + " and " + this.toppings[1] + " sandwich?");
  }
});

Hasan A Yousef
fuente
1

Aquí hay un ejemplo básico que explica la callback()función en JavaScript:

var x = 0;

function testCallBack(param1, param2, callback) {
  alert('param1= ' + param1 + ', param2= ' + param2 + ' X=' + x);
  if (callback && typeof(callback) === "function") {
    x += 1;
    alert("Calla Back x= " + x);
    x += 1;
    callback();
  }
}

testCallBack('ham', 'cheese', function() {
  alert("Function X= " + x);
});

JSFiddle

BERGUIGA Mohamed Amine
fuente
1

function checkCallback(cb) {
  if (cb || cb != '') {
    if (typeof window[cb] === 'undefined') alert('Callback function not found.');
    else window[cb].call(this, Arg1, Arg2);
  }
}

Aamir Afridi
fuente