function - ¿Cómo explicar las devoluciones de llamada en inglés simple? ¿En qué se diferencian de llamar a una función de otra función?
callback language-agnostic (30)
¿Cómo explicar las devoluciones de llamada en inglés simple?
En términos simples, una función de devolución de llamada es como un Trabajador que "devuelve la llamada" a su Administrador cuando ha completado una Tarea .
¿En qué se diferencian de llamar a una función de otra función tomando algo de contexto de la función de llamada?
Es cierto que está llamando a una función desde otra función, pero la clave es que la devolución de llamada se trata como un Objeto, por lo que puede cambiar a qué Función llamar según el estado del sistema (como el Patrón de diseño de estrategia).
¿Cómo se puede explicar su poder a un programador novato?
El poder de las devoluciones de llamada se puede ver fácilmente en los sitios web de estilo AJAX que necesitan extraer datos de un servidor. La descarga de los nuevos datos puede llevar algún tiempo. Sin las devoluciones de llamada, su interfaz de usuario completa se "congelaría" al descargar los nuevos datos, o tendría que actualizar toda la página en lugar de solo una parte de ella. Con una devolución de llamada, puede insertar una imagen "ahora cargando" y reemplazarla con los nuevos datos una vez que se haya cargado.
Algún código sin devolución de llamada:
function grabAndFreeze() {
showNowLoading(true);
var jsondata = getData(''http://yourserver.com/data/messages.json'');
/* User Interface ''freezes'' while getting data */
processData(jsondata);
showNowLoading(false);
do_other_stuff(); // not called until data fully downloaded
}
function processData(jsondata) { // do something with the data
var count = jsondata.results ? jsondata.results.length : 0;
$(''#counter_messages'').text([''Fetched'', count, ''new items''].join('' ''));
$(''#results_messages'').html(jsondata.results || ''(no new messages)'');
}
Con devolución de llamada:
Aquí hay un ejemplo con una devolución de llamada, utilizando getJSON de jQuery:
function processDataCB(jsondata) { // callback: update UI with results
showNowLoading(false);
var count = jsondata.results ? jsondata.results.length : 0;
$(''#counter_messages'').text([''Fetched'', count, ''new items''].join('' ''));
$(''#results_messages'').html(jsondata.results || ''(no new messages)'');
}
function grabAndGo() { // and don''t freeze
showNowLoading(true);
$(''#results_messages'').html(now_loading_image);
$.getJSON("http://yourserver.com/data/messages.json", processDataCB);
/* Call processDataCB when data is downloaded, no frozen User Interface! */
do_other_stuff(); // called immediately
}
Con cierre:
A menudo, la devolución de llamada necesita acceder al state
desde la función de llamada mediante un closure
, que es como el Trabajador que necesita obtener información del Administrador antes de que pueda completar su Tarea . Para crear el closure
, puede integrar la función para que vea los datos en el contexto de la llamada:
/* Grab messages, chat users, etc by changing dtable. Run callback cb when done.*/
function grab(dtable, cb) {
if (null == dtable) { dtable = "messages"; }
var uiElem = "_" + dtable;
showNowLoading(true, dtable);
$(''#results'' + uiElem).html(now_loading_image);
$.getJSON("http://yourserver.com/user/"+dtable+".json", cb || function (jsondata) {
// Using a closure: can "see" dtable argument and uiElem variables above.
var count = jsondata.results ? jsondata.results.length : 0,
counterMsg = [''Fetched'', count, ''new'', dtable].join('' ''),
// no new chatters/messages/etc
defaultResultsMsg = [''(no new '', dtable, '')''].join('''');
showNowLoading(false, dtable);
$(''#counter'' + uiElem).text(counterMsg);
$(''#results''+ uiElem).html(jsondata.results || defaultResultsMsg);
});
/* User Interface calls cb when data is downloaded */
do_other_stuff(); // called immediately
}
Uso:
// update results_chatters when chatters.json data is downloaded:
grab("chatters");
// update results_messages when messages.json data is downloaded
grab("messages");
// call myCallback(jsondata) when "history.json" data is loaded:
grab("history", myCallback);
Cierre
Finalmente, aquí hay una definición de closure
de Douglas Crockford :
Las funciones se pueden definir dentro de otras funciones. La función interna tiene acceso a las variables y parámetros de la función externa. Si una referencia a una función interna sobrevive (por ejemplo, como una función de devolución de llamada), las vars de la función externa también sobreviven.
Ver también:
¿Cómo explicar las devoluciones de llamada en inglés simple? ¿En qué se diferencian de llamar a una función de otra función tomando algo de contexto de la función de llamada? ¿Cómo se puede explicar su poder a un programador novato?
A menudo, una aplicación necesita ejecutar diferentes funciones basadas en su contexto / estado. Para esto, usamos una variable donde almacenaríamos la información sobre la función a llamar. De acuerdo con su necesidad, la aplicación establecerá esta variable con la información sobre la función que se llamará y llamará a la función utilizando la misma variable.
En javascript, el ejemplo está abajo. Aquí usamos el argumento del método como una variable donde almacenamos información sobre la función.
function processArray(arr, callback) {
var resultArr = new Array();
for (var i = arr.length-1; i >= 0; i--)
resultArr[i] = callback(arr[i]);
return resultArr;
}
var arr = [1, 2, 3, 4];
var arrReturned = processArray(arr, function(arg) {return arg * -1;});
// arrReturned would be [-1, -2, -3, -4]
En términos no programadores, una devolución de llamada es un espacio en blanco en un programa.
Un elemento común en muchos formularios en papel es "Persona a quien llamar en caso de emergencia". Hay una línea en blanco allí. Escribe el nombre y número de teléfono de alguien. Si ocurre una emergencia, entonces llaman a esa persona.
- Todos reciben el mismo formulario en blanco, pero
- Todos pueden escribir un número de contacto de emergencia diferente.
Esta es la clave. No cambia el formulario (el código, generalmente el de otra persona). Sin embargo, puede completar los datos faltantes ( su número).
Ejemplo 1:
Las devoluciones de llamada se utilizan como métodos personalizados, posiblemente para agregar / cambiar el comportamiento de un programa. Por ejemplo, tome algún código C que realice una función, pero no sepa cómo imprimir la salida. Todo lo que puede hacer es hacer una cadena. Cuando intenta averiguar qué hacer con la cadena, ve una línea en blanco. ¡Pero el programador le dio el espacio en blanco para escribir su devolución de llamada!
En este ejemplo, no utiliza un lápiz para rellenar un espacio en blanco en una hoja de papel, utiliza la función set_print_callback(the_callback)
.
- La variable en blanco en el módulo / código es la línea en blanco,
-
set_print_callback
es el lápiz, - y
the_callback
es su información que está completando.
Ahora has completado esta línea en blanco en el programa. Siempre que necesite imprimir la salida, verá esa línea en blanco y seguirá las instrucciones allí (es decir, llame a la función que puso allí). Prácticamente, esto permite la posibilidad de imprimir en la pantalla, en un archivo de registro, en una impresora, a través de una conexión de red, o cualquier combinación de los mismos. Has completado el espacio en blanco con lo que quieres hacer.
Ejemplo 2:
Cuando se le dice que necesita llamar a un número de emergencia, vaya a leer lo que está escrito en el formulario en papel y luego llame al número que leyó. Si esa línea está en blanco no se hará nada.
La programación gui funciona de la misma manera. Cuando se hace clic en un botón, el programa debe averiguar qué hacer a continuación. Va y busca la devolución de llamada. Resulta que esta devolución de llamada está en un espacio en blanco etiquetado "Esto es lo que haces cuando se hace clic en Button1"
La mayoría de los IDE llenarán automáticamente el espacio en blanco por usted (escriba el método básico) cuando lo solicite (por ejemplo, button1_clicked
). Sin embargo, ese espacio en blanco puede tener cualquier método, por favor . Puede llamar al método run_computations
o butter_the_biscuits
siempre que ponga el nombre de la devolución de llamada en el espacio en blanco adecuado. Puede poner "555-555-1212" en el número de emergencia. No tiene mucho sentido, pero es permisible.
Nota final: ¿Esa línea en blanco que está completando con la devolución de llamada? Se puede borrar y volver a escribir a voluntad. (Si debes o no es otra pregunta, pero eso es parte de su poder)
Hay dos puntos que explicar, uno es cómo funciona una devolución de llamada (pasar una función que puede ser llamada sin ningún conocimiento de su contexto), el otro para qué se usa (manejar eventos de forma asíncrona).
La analogía de esperar a que llegue un paquete que ha sido utilizada por otras respuestas es buena para explicar ambas. En un programa de computadora, le diría a la computadora que espere un paquete. Por lo general, ahora se sentaría allí y esperaría (y no haría nada más) hasta que llegue el paquete, posiblemente de forma indefinida si nunca llega. Para los humanos, esto suena tonto, pero sin más medidas, esto es totalmente natural para una computadora.
Ahora la devolución de llamada sería el timbre en la puerta de su casa. Usted proporciona al servicio de paquetes una manera de notificarle la llegada del paquete sin que ellos tengan que saber dónde (incluso si) está en la casa, o cómo funciona el timbre. (Por ejemplo, algunas "campanas" envían una llamada telefónica). Debido a que proporcionó una "función de devolución de llamada" que puede ser "llamada" en cualquier momento, fuera de contexto, ahora puede dejar de sentarse en el porche delantero y "manejar la evento "(de la llegada del paquete) cuando sea el momento.
Imagina que necesitas una función que devuelve 10 al cuadrado, así que escribes una función:
function tenSquared() {return 10*10;}
Luego necesitas 9 al cuadrado por lo que escribes otra función:
function nineSquared() {return 9*9;}
Eventualmente reemplazará todo esto con una función genérica:
function square(x) {return x*x;}
El mismo pensamiento exacto se aplica a las devoluciones de llamada. Usted tiene una función que hace algo y cuando termina las llamadas doA:
function computeA(){
...
doA(result);
}
Más adelante, querrá que la misma función se llame doB, en lugar de eso, podría duplicar toda la función:
function computeB(){
...
doB(result);
}
O puede pasar una función de devolución de llamada como una variable y solo tiene que tener la función una vez:
function compute(callback){
...
callback(result);
}
Entonces solo tienes que llamar a compute (doA) y compute (doB).
Además de simplificar el código, le permite al código asíncrono saber que se ha completado llamando a su función arbitraria al finalizar, de manera similar a cuando llama a alguien por teléfono y deja un número de devolución de llamada.
Imagina que un amigo se está yendo de tu casa y le dices "Llámame cuando llegues a casa para que sepa que llegaste a salvo"; eso es (literalmente) una llamada de vuelta . Eso es lo que es una función de devolución de llamada, independientemente del idioma. Desea que algún procedimiento le devuelva el control cuando haya completado alguna tarea, así que le asigna una función para que le devuelva la llamada.
En Python, por ejemplo,
grabDBValue( (lambda x: passValueToGUIWindow(x) ))
grabDBValue
podría escribirse para tomar solo un valor de una base de datos y luego permitirle especificar qué hacer realmente con el valor, por lo que acepta una función. No sabes cuándo o si grabDBValue
volverá, pero si / cuando lo hace, sabes lo que quieres que haga. Aquí, paso una función anónima (o lambda ) que envía el valor a una ventana GUI. Podría fácilmente cambiar el comportamiento del programa haciendo esto:
grabDBValue( (lambda x: passToLogger(x) ))
Las devoluciones de llamadas funcionan bien en idiomas en los que las funciones son valores de primera clase , como los enteros habituales, cadenas de caracteres, booleanos, etc. En C, puede "pasar" una función pasando un puntero a ella y la persona que llama puede usarla; en Java, la persona que llama solicitará una clase estática de cierto tipo con un determinado nombre de método, ya que no hay funciones ("métodos", realmente) fuera de las clases; y en la mayoría de los otros lenguajes dinámicos, puede pasar una función con una sintaxis simple.
Protip:
En idiomas con alcance léxico (como Scheme o Perl), puedes hacer un truco como este:
my $var = 2;
my $val = someCallerBackFunction(sub callback { return $var * 3; });
# Perlistas note: I know the sub doesn''t need a name, this is for illustration
$val
en este caso será 6
porque la devolución de llamada tiene acceso a las variables declaradas en el entorno léxico donde se definió. El alcance léxico y las devoluciones de llamada anónimas son una combinación poderosa que justifica un estudio adicional para el programador novato.
Imaginemos que tenías una tarea potencialmente larga: obtén los nombres de las cinco primeras personas únicas que te encuentres. Esto podría llevar días si estoy en un área escasamente poblada. No estás realmente interesado en sentarte en tus manos mientras estoy corriendo, así que dices: "Cuando tengas la lista, llámame al celular y léemela. Aquí está el número".
Me ha dado una referencia de devolución de llamada, una función que se supone que debo ejecutar para entregar más procesamiento.
En JavaScript podría verse algo como esto:
var lottoNumbers = [];
var callback = function(theNames) {
for (var i=0; i<theNames.length; i++) {
lottoNumbers.push(theNames[i].length);
}
};
db.executeQuery("SELECT name " +
"FROM tblEveryOneInTheWholeWorld " +
"ORDER BY proximity DESC " +
"LIMIT 5", callback);
while (lottoNumbers.length < 5) {
playGolf();
}
playLotto(lottoNumbers);
Esto probablemente podría mejorarse de muchas maneras. Por ejemplo, puede proporcionar una segunda devolución de llamada: si termina demorando más de una hora, llame al teléfono rojo y dígale a la persona que las respuestas han caducado.
Johny, el programador, necesita una grapadora, por lo que baja al departamento de suministros de oficina y solicita una. Después de completar el formulario de solicitud, puede quedarse allí y esperar a que el empleado vaya a buscar la grapadora en el almacén (como una llamada de función de bloqueo). ) o ir a hacer otra cosa mientras tanto.
ya que esto generalmente toma tiempo, johny pone una nota junto con el formulario de solicitud pidiéndoles que lo llamen cuando la grapadora esté lista para ser recogida, así que mientras tanto él puede hacer otra cosa como echar una siesta en su escritorio.
Me sorprende ver a tantas personas inteligentes que no logran enfatizar la realidad de que la palabra "devolución de llamada" se ha utilizado de dos maneras inconsistentes.
Ambas formas implican la personalización de una función al pasar una funcionalidad adicional (una definición de la función, anónima o nombrada) a una función existente. es decir.
customizableFunc(customFunctionality)
Si la funcionalidad personalizada simplemente se conecta al bloque de código, ha personalizado la función, como así.
customizableFucn(customFunctionality) {
var data = doSomthing();
customFunctionality(data);
...
}
Aunque este tipo de funcionalidad inyectada a menudo se llama "devolución de llamada", no hay nada contingente al respecto. Un ejemplo muy obvio es el método forEach en el que se proporciona una función personalizada como un argumento que se aplica a cada elemento de una matriz para modificar la matriz.
Pero esto es fundamentalmente distinto del uso de las funciones de "devolución de llamada" para la programación asíncrona , como en AJAX o node.js o simplemente para asignar funcionalidad a eventos de interacción del usuario (como los clics del mouse). En este caso, la idea general es esperar a que ocurra un evento contingente antes de ejecutar la funcionalidad personalizada. Esto es obvio en el caso de la interacción del usuario, pero también es importante en los procesos de E / S (entrada / salida) que pueden llevar tiempo, como leer archivos del disco. Aquí es donde el término "devolución de llamada" tiene el sentido más obvio. Una vez que se inicia un proceso de E / S (como pedir que se lea un archivo del disco o un servidor para devolver datos de una solicitud http), un programa asíncrono no espera a que termine. Puede continuar con las tareas programadas a continuación, y solo responder con la funcionalidad personalizada después de que se le haya notificado que el archivo de lectura o la solicitud http se completó (o que falló) y que los datos están disponibles para la funcionalidad personalizada. Es como llamar a un negocio por teléfono y dejar su número de "devolución de llamada", para que puedan llamarlo cuando haya alguien disponible para responderle. Eso es mejor que colgar en la línea para saber quién es el tiempo y no poder atender otros asuntos.
El uso asíncrono implica inherentemente algunos medios para escuchar el evento deseado (p. Ej., La finalización del proceso de I / O) para que, cuando ocurra (y solo cuando ocurra), se ejecute la funcionalidad personalizada de "devolución de llamada". En el ejemplo obvio de AJAX, cuando los datos realmente llegan del servidor, la función de "devolución de llamada" se activa para usar esos datos para modificar el DOM y, por lo tanto, volver a dibujar la ventana del navegador en esa medida.
Recordar. Algunas personas usan la palabra "devolución de llamada" para referirse a cualquier tipo de funcionalidad personalizada que se pueda inyectar en una función existente como argumento. Pero, al menos para mí, el uso más apropiado de la palabra es cuando la función de "devolución de llamada" inyectada se usa de forma asíncrona, que se ejecuta solo cuando se produce un evento del que está esperando ser notificado.
Normalmente enviamos variables a funciones. Supongamos que tiene una tarea en la que la variable debe procesarse antes de ser presentada como un argumento; puede usar la devolución de llamada.
function1(var1, var2)
es la forma habitual.
¿Qué var2
si deseo que var2
se procese y luego se envíe como un argumento? function1(var1, function2(var2))
Este es un tipo de devolución de llamada: donde function2
ejecuta algún código y devuelve una variable a la función inicial.
Para enseñar las devoluciones de llamada, primero hay que enseñar el puntero. Una vez que los alumnos comprendan la idea de apuntar a una variable, la idea de las devoluciones de llamada será más fácil. Suponiendo que está utilizando C / C ++, estos pasos pueden seguirse.
- Primero, muestre a sus alumnos cómo usar y manipular variables usando punteros junto con los identificadores de variables normales.
- Luego, enséñeles que hay cosas que se pueden hacer solo con punteros (como pasar una variable por referencia).
- Luego cuénteles cómo el código o las funciones ejecutables son como otros datos (o variables) en la memoria. Por lo tanto, las funciones también tienen direcciones o punteros.
- Luego muéstreles cómo se pueden llamar a las funciones con punteros de función y dígales que se llaman devoluciones de llamada.
- Ahora, la pregunta es, ¿por qué todos estos problemas para llamar a algunas funciones? ¿Cuál es el beneficio? Al igual que los punteros de datos, el puntero de función también conocido como devolución de llamada tiene algunas ventajas sobre el uso de identificadores normales.
- La primera es que los identificadores de función o los nombres de función no pueden utilizarse como datos normales. Quiero decir, no puede crear una estructura de datos con funciones (como una matriz o una lista de funciones vinculadas). Pero con las devoluciones de llamada, puede hacer una matriz, una lista enlazada o usarla con otros datos como en el diccionario de pares o árboles clave-valor, o cualquier otra cosa. Este es un poderoso beneficio. Y otros beneficios son en realidad hijo de éste.
- El uso más común de las devoluciones de llamada se ve en la programación del controlador de eventos. Donde una o más funciones se ejecutan en base a alguna señal entrante. Con las devoluciones de llamada, se puede mantener un diccionario para asignar señales con devoluciones de llamada. Entonces, la resolución de la señal de entrada y la ejecución del código correspondiente se vuelven mucho más fáciles.
- El segundo uso de devoluciones de llamada que vienen en mi mente son las funciones de orden superior. Las funciones que toman otras funciones como argumentos de entrada. Y para enviar funciones como argumentos, necesitamos callbacks. Un ejemplo puede ser una función que toma una matriz y una devolución de llamada. Luego realiza la devolución de llamada en cada uno de los elementos de la matriz y devuelve los resultados en otra matriz. Si le pasamos a la función una devolución de llamada duplicada, obtenemos una matriz de doble valor. Si pasamos un callback de cuadratura, obtenemos cuadrados. Para las raíces cuadradas, simplemente envíe la devolución de llamada adecuada. Esto no se puede hacer con las funciones normales.
Podría haber muchas más cosas. Involucre a los alumnos y ellos lo descubrirán. Espero que esto ayude.
Siempre es mejor empezar con un ejemplo :).
Supongamos que tiene dos módulos A y B.
Desea que se notifique al módulo A cuando ocurra algún evento / condición en el módulo B. Sin embargo, el módulo B no tiene idea acerca de su módulo A. Todo lo que sabe es una dirección a una función particular (del módulo A) a través de un puntero de función que es Proporcionado por el módulo A.
Entonces, todo lo que B tiene que hacer ahora, es "devolver la llamada" al módulo A cuando ocurre un evento / condición particular usando el puntero de función. A puede hacer más procesamiento dentro de la función de devolución de llamada.
*) Una clara ventaja aquí es que está abstrayendo todo sobre el módulo A del módulo B. El módulo B no tiene que preocuparse por quién / qué es el módulo A.
Sin la devolución de llamada ni otros recursos de programación especiales (como subprocesos y otros), un programa es exactamente una secuencia de instrucciones que se ejecutan secuencialmente una tras otra , e incluso con un tipo de "comportamiento dinámico" determinado por ciertas condiciones, todos los escenarios posibles Se programará previamente .
Entonces, si necesitamos proporcionar un comportamiento dinámico real a un programa, podemos usar la devolución de llamada. Con la devolución de llamada, puede ordenar por parámetros, un programa para llamar a otro programa que proporciona algunos parámetros definidos previamente y puede esperar algunos resultados ( este es el contrato o la firma de la operación ), por lo que estos resultados pueden ser producidos / procesados por un programa de terceros que no fue No se conocía previamente.
Esta técnica es la base del polimorfismo aplicado a programas, funciones, objetos y todas las demás unidades de código ejecutadas por computadoras.
El mundo humano utilizado como ejemplo para la devolución de llamada se explica bien cuando está haciendo un trabajo, supongamos que es un pintor ( aquí usted es el programa principal, que pinta ) y llame a su cliente a veces para pedirle que apruebe el resultado de su trabajo. Entonces, él decide si la imagen es buena ( su cliente es el programa de terceros ).
En el ejemplo anterior, usted es un pintor y "delegue" a otros el trabajo para aprobar el resultado, la imagen es el parámetro, y cada nuevo cliente (la "función" de devolución de llamada) cambia el resultado de su trabajo y decide qué quiere. sobre la imagen ( la decisión tomada por los clientes es el resultado devuelto de la "función de devolución de llamada" ).
Espero que esta explicación pueda ser útil.
Te sientes mal por lo que vas al médico. Él te examina y determina que necesitas algún medicamento. Prescribe algunos medicamentos y envía la receta a su farmacia local. Ve a casa. Más tarde, su farmacia llama para informarle que su receta está lista. Ve y recógelo.
Tienes algún código que quieres ejecutar. Normalmente, cuando lo llama, está esperando que termine antes de continuar (lo que puede hacer que su aplicación se vuelva gris / produzca un tiempo de giro para un cursor).
Un método alternativo es ejecutar este código en paralelo y continuar con su propio trabajo. Pero, ¿qué pasa si su código original necesita hacer cosas diferentes según la respuesta del código al que llamó? Bueno, en ese caso, puede pasar el nombre / la ubicación del código al que desea llamar cuando haya terminado. Este es un "call back".
Código normal: solicitar información-> Información de proceso-> Tratar con los resultados del procesamiento-> Continuar haciendo otras cosas.
Con devoluciones de llamada: solicite información-> Información de proceso-> Continúe haciendo otras cosas. Y en algún punto posterior-> Tratar con los resultados del procesamiento.
Una devolución de llamada es una función que será llamada por una segunda función. Esta segunda función no sabe de antemano qué función llamará. Así que la identidad de la función de devolución de llamada se almacena en algún lugar, o se pasa a la segunda función como un parámetro. Esta "identidad", según el lenguaje de programación, puede ser la dirección de la devolución de llamada, o algún otro tipo de puntero, o puede ser el nombre de la función. El principal es el mismo, almacenamos o pasamos alguna información que identifica inequívocamente la función.
Cuando llega el momento, la segunda función puede llamar a la devolución de llamada, suministrando parámetros según las circunstancias en ese momento. Incluso podría elegir la devolución de llamada de un conjunto de posibles devoluciones de llamada. El lenguaje de programación debe proporcionar algún tipo de sintaxis para permitir que la segunda función llame a la devolución de llamada, sabiendo su "identidad".
Este mecanismo tiene muchos usos posibles. Con las devoluciones de llamada, el diseñador de una función puede permitir que se personalice haciendo que llame a cualquier devolución de llamada que se proporcione. Por ejemplo, una función de clasificación puede tomar una devolución de llamada como un parámetro, y esta devolución de llamada puede ser una función para comparar dos elementos para decidir cuál viene primero.
Por cierto, dependiendo del lenguaje de programación, la palabra "función" en la discusión anterior podría ser reemplazada por "bloque", "cierre", "lambda", etc.
Una explicación metafórica:
Tengo un paquete que quiero entregar a un amigo, y también quiero saber cuándo lo recibirá mi amigo.
Así que llevo el paquete a la oficina de correos y les pido que lo entreguen. Si quiero saber cuándo mi amigo recibe el paquete, tengo dos opciones:
(a) Puedo esperar en la oficina de correos hasta que se entregue.
(b) Recibiré un correo electrónico cuando sea entregado.
La opción (b) es análoga a una devolución de llamada.
Voy a tratar de mantener esto muerto simple. Una "devolución de llamada" es cualquier función llamada por otra función que toma la primera función como parámetro. Muchas veces, una "devolución de llamada" es una función que se llama cuando ocurre algo . Ese algo puede ser llamado un "evento" en lenguaje de programador.
Imagine este escenario: está esperando un paquete en un par de días. El paquete es un regalo para tu vecino. Por lo tanto, una vez que obtenga el paquete, querrá que sea entregado a los vecinos. Usted está fuera de la ciudad, por lo que deja instrucciones para su cónyuge.
Podría decirles que obtengan el paquete y se lo lleven a los vecinos. Si su cónyuge fuera tan estúpido como una computadora, se sentarían en la puerta y esperarían el paquete hasta que llegara (NO HACER NADA MÁS) y luego, una vez que llegara, lo llevarían a los vecinos. Pero hay una mejor manera. Dígale a su cónyuge que UNA VEZ que reciben el paquete, deben traerlo sobre los vecinos. Entonces, pueden ir por la vida normalmente HASTA que reciban el paquete.
En nuestro ejemplo, la recepción del paquete es el "evento" y llevarlo a los vecinos es la "devolución de llamada". Su cónyuge "ejecuta" sus instrucciones para traer el paquete solo cuando llega el paquete. ¡Mucho mejor!
Este tipo de pensamiento es obvio en la vida diaria, pero las computadoras no tienen el mismo sentido común. Considera cómo los programadores escriben normalmente en un archivo:
fileObject = open(file)
# now that we have WAITED for the file to open, we can write to it
fileObject.write("We are writing to the file.")
# now we can continue doing the other, totally unrelated things our program does
Aquí, esperamos a que se abra el archivo, antes de escribirlo. ¡Esto "bloquea" el flujo de ejecución, y nuestro programa no puede hacer ninguna de las otras cosas que podría necesitar! ¿Y si pudiéramos hacer esto en su lugar?
# we pass writeToFile (A CALLBACK FUNCTION!) to the open function
fileObject = open(file, writeToFile)
# execution continues flowing -- we don''t wait for the file to be opened
# ONCE the file is opened we write to it, but while we wait WE CAN DO OTHER THINGS!
Resulta que lo hacemos con algunos lenguajes y marcos. ¡Es genial! Echa un vistazo a Node.js para obtener un poco de práctica real con este tipo de pensamiento.
Las devoluciones de llamada se describen más fácilmente en términos del sistema telefónico. Una llamada de función es análoga a llamar a alguien por teléfono, hacerle una pregunta, obtener una respuesta y colgar; agregar una devolución de llamada cambia la analogía para que, después de hacerle una pregunta, también le dé su nombre y número para que pueda devolverle la llamada con la respuesta. - Paul Jakubik, "Implementaciones de devolución de llamada en C ++"
¿Qué es una función de devolución de llamada?
La respuesta simple a esta primera pregunta es que una función de devolución de llamada es una función que se llama a través de un puntero de función. Si pasa el puntero (dirección) de una función como un argumento a otro, cuando ese puntero se usa para llamar a la función que señala, se dice que se realiza una devolución de llamada.
La función de devolución de llamada es difícil de rastrear, pero a veces es muy útil. Especialmente cuando estás diseñando bibliotecas. La función de devolución de llamada es como pedirle a su usuario que le dé un nombre de función, y la llamará bajo ciertas condiciones.
Por ejemplo, escribe un temporizador de devolución de llamada. Le permite especificar la duración y la función a la que desea llamar, y la función se devolverá en consecuencia. "Ejecutar mi función () cada 10 segundos por 5 veces"
O puede crear un directorio de funciones, pasar una lista de nombres de funciones y pedir a la biblioteca que devuelva la llamada en consecuencia. "Devolución de llamada correcta () si es correcta, falla de devolución de llamada () si falla".
Veamos un ejemplo de puntero de función simple
void cbfunc()
{
printf("called");
}
int main ()
{
/* function pointer */
void (*callback)(void);
/* point to your callback function */
callback=(void *)cbfunc;
/* perform callback */
callback();
return 0;
}
¿Cómo pasar argumento a la función de devolución de llamada?
Observó que la función de puntero para implementar devoluciones de llamada en void *, lo que indica que puede tomar cualquier tipo de variable, incluida la estructura. Por lo tanto puede pasar en múltiples argumentos por estructura.
typedef struct myst
{
int a;
char b[10];
}myst;
void cbfunc(myst *mt)
{
fprintf(stdout,"called %d %s.",mt->a,mt->b);
}
int main()
{
/* func pointer */
void (*callback)(void *); //param
myst m;
m.a=10;
strcpy(m.b,"123");
callback = (void*)cbfunc; /* point to callback function */
callback(&m); /* perform callback and pass in the param */
return 0;
}
Creo que es una tarea bastante fácil de explicar.
Al principio, las devoluciones de llamada son solo funciones ordinarias.
Y más adelante, llamamos a esta función (llamémosla A) desde dentro de otra función (llamémosla B).
La magia de esto es que yo decido, qué función debe ser llamada por la función desde fuera B.
En el momento en que escribo la función BI, no sé a qué función de devolución de llamada se debe llamar. Cuando llamo a la función BI, también le digo a esta función que llame a la función A. Eso es todo.
Esto en términos de descargar una página web:
Su programa se ejecuta en un teléfono celular y solicita la página web http://www.google.com . Si escribe su programa de forma síncrona, la función que escriba para descargar los datos se ejecutará de forma continua hasta que se descarguen todos los datos. Esto significa que su interfaz de usuario no se actualizará y, básicamente, aparecerá congelada. Si escribe su programa utilizando devoluciones de llamada, solicite los datos y diga "ejecutar esta función cuando haya terminado". Esto permite que la interfaz de usuario aún permita la interacción del usuario mientras se descarga el archivo. Una vez que la página web ha finalizado la descarga, se llama a su función de resultados (devolución de llamada) y puede manejar los datos.
Básicamente, le permite solicitar algo y continuar ejecutando mientras espera el resultado. Una vez que el resultado regresa a usted a través de una función de devolución de llamada, puede retomar la operación donde se detuvo.
Una devolución de llamada es un método que está programado para ejecutarse cuando se cumple una condición.
Un ejemplo del "mundo real" es una tienda de videojuegos local. Estás esperando Half-Life 3. En lugar de ir a la tienda todos los días para ver si está, registras tu correo electrónico en una lista para recibir una notificación cuando el juego esté disponible. El correo electrónico se convierte en su "devolución de llamada" y la condición a cumplir es la disponibilidad del juego.
Un ejemplo de "programadores" es una página web donde desea realizar una acción cuando se hace clic en un botón. Registra un método de devolución de llamada para un botón y continúa haciendo otras tareas. Cuando / si el usuario selecciona el botón, el navegador mirará la lista de devoluciones de llamadas para ese evento y llamará a su método.
Una devolución de llamada es una forma de manejar eventos de forma asíncrona. Nunca se puede saber cuándo se ejecutará la devolución de llamada, o si se ejecutará en absoluto. La ventaja es que libera sus ciclos de programa y CPU para realizar otras tareas mientras espera la respuesta.
“En la programación de computadoras, una devolución de llamada es una referencia al código ejecutable, o una pieza de código ejecutable, que se pasa como un argumento a otro código. Esto permite que una capa de software de nivel inferior llame a una subrutina (o función) definida en una capa de nivel superior ". - Wikipedia
Devolución de llamada en C usando puntero de función
En C, la devolución de llamada se implementa utilizando el puntero de función. Puntero de función: como su nombre indica, es un puntero a una función.
Por ejemplo, int (* ptrFunc) ();
Aquí, ptrFunc es un puntero a una función que no toma argumentos y devuelve un entero. NO olvide poner entre paréntesis, de lo contrario el compilador asumirá que ptrFunc es un nombre de función normal, que no toma nada y devuelve un puntero a un entero.
Aquí hay un código para demostrar el puntero de la función.
#include<stdio.h>
int func(int, int);
int main(void)
{
int result1,result2;
/* declaring a pointer to a function which takes
two int arguments and returns an integer as result */
int (*ptrFunc)(int,int);
/* assigning ptrFunc to func''s address */
ptrFunc=func;
/* calling func() through explicit dereference */
result1 = (*ptrFunc)(10,20);
/* calling func() through implicit dereference */
result2 = ptrFunc(10,20);
printf("result1 = %d result2 = %d/n",result1,result2);
return 0;
}
int func(int x, int y)
{
return x+y;
}
Ahora intentemos entender el concepto de devolución de llamada en C usando el puntero de función.
El programa completo tiene tres archivos: callback.c, reg_callback.h y reg_callback.c.
/* callback.c */
#include<stdio.h>
#include"reg_callback.h"
/* callback function definition goes here */
void my_callback(void)
{
printf("inside my_callback/n");
}
int main(void)
{
/* initialize function pointer to
my_callback */
callback ptr_my_callback=my_callback;
printf("This is a program demonstrating function callback/n");
/* register our callback function */
register_callback(ptr_my_callback);
printf("back inside main program/n");
return 0;
}
/* reg_callback.h */
typedef void (*callback)(void);
void register_callback(callback ptr_reg_callback);
/* reg_callback.c */
#include<stdio.h>
#include"reg_callback.h"
/* registration goes here */
void register_callback(callback ptr_reg_callback)
{
printf("inside register_callback/n");
/* calling our callback function my_callback */
(*ptr_reg_callback)();
}
Si ejecutamos este programa, la salida será
Este es un programa que muestra la función de devolución de llamada dentro de register_callback dentro de my_callback dentro de programa principal
La función de capa superior llama a una función de capa inferior como una llamada normal y el mecanismo de devolución de llamada permite a la función de capa inferior llamar a la función de capa superior a través de un puntero a una función de devolución de llamada.
Devolución de llamada en la interfaz de uso de Java
Java no tiene el concepto de puntero de función. Implementa el mecanismo de devolución de llamada a través de su mecanismo de interfaz. Aquí, en lugar de un puntero de función, declaramos una interfaz con un método que se llamará cuando el destinatario finalice su tarea.
Déjame demostrarlo a través de un ejemplo:
La interfaz de devolución de llamada
public interface Callback
{
public void notify(Result result);
}
La persona que llama o la clase de nivel superior
public Class Caller implements Callback
{
Callee ce = new Callee(this); //pass self to the callee
//Other functionality
//Call the Asynctask
ce.doAsynctask();
public void notify(Result result){
//Got the result after the callee has finished the task
//Can do whatever i want with the result
}
}
La función Callee o la capa inferior
public Class Callee {
Callback cb;
Callee(Callback cb){
this.cb = cb;
}
doAsynctask(){
//do the long running task
//get the result
cb.notify(result);//after the task is completed, notify the caller
}
}
Devolución de llamada utilizando el patrón EventListener
- Elemento de lista
Este patrón se utiliza para notificar a 0 a n números de observadores / oyentes que una tarea particular ha finalizado
- Elemento de lista
La diferencia entre el mecanismo de devolución de llamada y el mecanismo EventListener / Observer es que en la devolución de llamada, la persona que llama notifica a la persona que llama solo, mientras que en Eventlisener / Observer, la persona llamada puede notificar a cualquier persona interesada en ese evento (la notificación puede dirigirse a otras partes del aplicación que no ha activado la tarea)
Déjame explicarlo a través de un ejemplo.
La interfaz de eventos
public interface Events {
public void clickEvent();
public void longClickEvent();
}
Widget de clase
package com.som_itsolutions.training.java.exampleeventlistener;
import java.util.ArrayList;
import java.util.Iterator;
public class Widget implements Events{
ArrayList<OnClickEventListener> mClickEventListener = new ArrayList<OnClickEventListener>();
ArrayList<OnLongClickEventListener> mLongClickEventListener = new ArrayList<OnLongClickEventListener>();
@Override
public void clickEvent() {
// TODO Auto-generated method stub
Iterator<OnClickEventListener> it = mClickEventListener.iterator();
while(it.hasNext()){
OnClickEventListener li = it.next();
li.onClick(this);
}
}
@Override
public void longClickEvent() {
// TODO Auto-generated method stub
Iterator<OnLongClickEventListener> it = mLongClickEventListener.iterator();
while(it.hasNext()){
OnLongClickEventListener li = it.next();
li.onLongClick(this);
}
}
public interface OnClickEventListener
{
public void onClick (Widget source);
}
public interface OnLongClickEventListener
{
public void onLongClick (Widget source);
}
public void setOnClickEventListner(OnClickEventListener li){
mClickEventListener.add(li);
}
public void setOnLongClickEventListner(OnLongClickEventListener li){
mLongClickEventListener.add(li);
}
}
Botón de clase
public class Button extends Widget{
private String mButtonText;
public Button (){
}
public String getButtonText() {
return mButtonText;
}
public void setButtonText(String buttonText) {
this.mButtonText = buttonText;
}
}
Class Checkbox
public class CheckBox extends Widget{
private boolean checked;
public CheckBox() {
checked = false;
}
public boolean isChecked(){
return (checked == true);
}
public void setCheck(boolean checked){
this.checked = checked;
}
}
Clase de actividad
package com.som_itsolutions.training.java.exampleeventlistener;
public class Activity implements Widget.OnClickEventListener
{
public Button mButton;
public CheckBox mCheckBox;
private static Activity mActivityHandler;
public static Activity getActivityHandle(){
return mActivityHandler;
}
public Activity ()
{
mActivityHandler = this;
mButton = new Button();
mButton.setOnClickEventListner(this);
mCheckBox = new CheckBox();
mCheckBox.setOnClickEventListner(this);
}
public void onClick (Widget source)
{
if(source == mButton){
mButton.setButtonText("Thank you for clicking me...");
System.out.println(((Button) mButton).getButtonText());
}
if(source == mCheckBox){
if(mCheckBox.isChecked()==false){
mCheckBox.setCheck(true);
System.out.println("The checkbox is checked...");
}
else{
mCheckBox.setCheck(false);
System.out.println("The checkbox is not checked...");
}
}
}
public void doSomeWork(Widget source){
source.clickEvent();
}
}
Otra clase
public class OtherClass implements Widget.OnClickEventListener{
Button mButton;
public OtherClass(){
mButton = Activity.getActivityHandle().mButton;
mButton.setOnClickEventListner(this);//interested in the click event //of the button
}
@Override
public void onClick(Widget source) {
if(source == mButton){
System.out.println("Other Class has also received the event notification...");
}
}
Clase principal
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
Activity a = new Activity();
OtherClass o = new OtherClass();
a.doSomeWork(a.mButton);
a.doSomeWork(a.mCheckBox);
}
}
Como puede ver en el código anterior, tenemos una interfaz llamada eventos que básicamente lista todos los eventos que pueden suceder para nuestra aplicación. La clase Widget es la clase base para todos los componentes de la interfaz de usuario como Button, Checkbox. Estos componentes de la interfaz de usuario son los objetos que realmente reciben los eventos del código de marco. La clase de widgets implementa la interfaz de Eventos y también tiene dos interfaces anidadas, a saber, OnClickEventListener y OnLongClickEventListener
Estas dos interfaces son responsables de escuchar los eventos que pueden ocurrir en los componentes de la interfaz de usuario derivados del widget, como Button o Checkbox. Entonces, si comparamos este ejemplo con el ejemplo anterior de devolución de llamada que utiliza la interfaz Java, estas dos interfaces funcionan como la interfaz de devolución de llamada. Así que el código de nivel superior (Actividad aquí) implementa estas dos interfaces. Y siempre que se produzca un evento en un widget, se llamará al código de nivel superior (o al método de estas interfaces implementado en el código de nivel superior, que se encuentra aquí Actividad).
Ahora permítanme hablar sobre la diferencia básica entre el patrón de Devolución de llamada y Eventlistener. Como hemos mencionado que al utilizar la devolución de llamada, la persona que llama puede notificar solo a una persona que llama. Pero en el caso del patrón EventListener, cualquier otra parte o clase de la aplicación puede registrarse para los eventos que pueden ocurrir en el botón o casilla de verificación. El ejemplo de este tipo de clase es OtherClass. Si ve el código de OtherClass, encontrará que se ha registrado como oyente del evento de clic que puede ocurrir en el botón definido en la actividad. Una parte interesante es que, además de la Actividad (el Llamante), esta Otra Clase también será notificada cuando ocurra el evento de clic en el Botón.
En inglés simple una devolución de llamada es una promesa. Joe, Jane, David y Samantha comparten un viaje compartido para trabajar. Joe está conduciendo hoy. Jane, David y Samantha tienen un par de opciones:
- Revisa la ventana cada 5 minutos para ver si Joe está fuera.
- Sigue haciendo lo suyo hasta que Joe toque el timbre de la puerta.
Opción 1: Esto es más como un ejemplo de sondeo en el que Jane se quedaría atascada en un "bucle" para verificar si Joe está afuera. Jane no puede hacer nada más mientras tanto.
Opción 2: Este es el ejemplo de devolución de llamada. Jane le dice a Joe que toque el timbre cuando está afuera. Ella le da una "función" para que suene el timbre de la puerta. Joe no necesita saber cómo funciona el timbre de la puerta o dónde está, solo necesita cumplir esa función, es decir, tocar el timbre de la puerta cuando está allí.
Las devoluciones de llamada son impulsadas por "eventos". En este ejemplo, el "evento" es la llegada de Joe. En Ajax, por ejemplo, los eventos pueden ser "éxito" o "error" de la solicitud asíncrona y cada uno puede tener devoluciones de llamada iguales o diferentes.
En términos de aplicaciones JavaScript y devoluciones de llamada. También necesitamos entender los "cierres" y el contexto de la aplicación. A lo que se refiere "esto" puede confundir fácilmente a los desarrolladores de JavaScript. En este ejemplo, dentro del método "ring_the_door_bell ()" / devolución de llamada de cada persona, es posible que haya otros métodos que cada persona deba hacer en función de su rutina matutina ex. "apagar la televisión()". Nos gustaría que "esto" se refiera al objeto "Jane" o al objeto "David" para que cada uno pueda configurar lo que sea necesario antes de que Joe los recoja. Aquí es donde configurar la devolución de llamada con Joe requiere parodiar el método para que "esto" se refiera al objeto correcto.
¡Espero que ayude!
Las devoluciones de llamada le permiten insertar su propio código en otro bloque de código que se ejecutará en otro momento, lo que modifica o agrega el comportamiento de ese otro bloque de código para satisfacer sus necesidades. Obtiene flexibilidad y personalización al mismo tiempo que puede tener un código más mantenible.
Menos código físico = más fácil de mantener y cambiar = menos tiempo = más valor comercial = maravilla.
Por ejemplo, en javascript, utilizando Underscore.js, puedes encontrar todos los elementos pares en una matriz como esta:
var evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
=> [2, 4, 6]
Ejemplo cortesía de Underscore.js: http://documentcloud.github.com/underscore/#filter
Piense en un método como dar una tarea a un compañero de trabajo. Una tarea simple podría ser la siguiente:
Solve these equations:
x + 2 = y
2 * x = 3 * y
Su compañero de trabajo hace diligentemente los cálculos y le da el siguiente resultado:
x = -6
y = -4
Pero su compañero de trabajo tiene un problema, no siempre entiende las notaciones, como las que ^
él hace, pero las entiende por su descripción. Tal como exponent
. Cada vez que encuentra uno de estos recuperas lo siguiente:
I don''t understand "^"
Esto requiere que vuelva a escribir todo el conjunto de instrucciones después de explicar qué significa el personaje para su compañero de trabajo, y no siempre recuerda entre las preguntas. Y él también tiene dificultades para recordar tus consejos, como solo preguntarme. Sin embargo, siempre sigue sus instrucciones escritas lo mejor que puede.
Piensas en una solución, solo agregas lo siguiente a todas tus instrucciones:
If you have any questions about symbols, call me at extension 1234 and I will tell you its name.
Ahora, cada vez que tiene un problema, lo llama y le pregunta, en lugar de darle una mala respuesta y reiniciar el proceso.
Sencillo y simple: una devolución de llamada es una función que le das a otra función, para que pueda llamarlo .
Por lo general, se llama cuando se completa alguna operación. Como crea la devolución de llamada antes de asignarla a la otra función, puede inicializarla con información de contexto del sitio de la llamada. Por eso se llama a call * back *: la primera función vuelve a llamar al contexto desde el que se llamó.
Una devolución de llamada es un sobre estampado con su dirección. Cuando llamas a una función, es como enviar una carta. Si desea que esa función llame a otra función, proporcione esa información en forma de una referencia o dirección.
[editado] cuando tenemos dos funciones, digamos functionA y functionB , si functionA depende de functionB .
luego llamamos a functionB como una función de devolución de llamada . Esto se usa ampliamente en Spring Framework.