En programación informática , una devolución de llamada es una función que se almacena como datos (una referencia ) y está diseñada para ser llamada por otra función, generalmente de vuelta a la capa de abstracción original .
Una función que acepta un parámetro de devolución de llamada puede estar diseñada para devolver la llamada antes de regresar a su llamador, lo que se conoce como sincrónica o bloqueante . La función que acepta una devolución de llamada puede estar diseñada para almacenar la devolución de llamada de modo que se pueda volver a llamar después de regresar, lo que se conoce como asincrónica , no bloqueante o diferida .
Los lenguajes de programación admiten devoluciones de llamadas de diferentes maneras, como punteros de función , expresiones lambda y bloques .
Para ayudar a comprender el concepto, la siguiente es una analogía de la vida real.
Un cliente visita una tienda para hacer un pedido. Es como la primera llamada.
El cliente entrega al empleado una lista de artículos, un cheque para cubrir su costo e instrucciones de entrega. Estos son los parámetros de la primera llamada, incluida la devolución de la llamada, que son las instrucciones de entrega. Se entiende que se cobrará el cheque y que se seguirán las instrucciones.
Cuando el personal puede, entrega los artículos según las instrucciones, lo que es como llamar para devolver la llamada.
Cabe destacar que no es necesario que la entrega la realice el empleado que tomó el pedido. No es necesario que la función que aceptó la devolución de llamada como parámetro invoque una devolución de llamada.
Además, no es necesario que la entrega se realice directamente al cliente. No es necesario que una devolución de llamada se realice a la función que realiza la llamada. De hecho, una función generalmente no se haría pasar por una devolución de llamada. Algunos consideran que el uso de " back" es engañoso, ya que la llamada (generalmente) no se realiza al llamador original, como ocurre en el caso de una llamada telefónica .
Una devolución de llamada de bloqueo se ejecuta en el contexto de ejecución de la función que pasa la devolución de llamada. Una devolución de llamada diferida puede ejecutarse en un contexto diferente, como durante una interrupción o desde un subproceso . Como tal, una devolución de llamada diferida se puede utilizar para la sincronización y la delegación de trabajo a otro subproceso.
Se puede utilizar una devolución de llamada para el manejo de eventos. A menudo, el código que se consume registra una devolución de llamada para un tipo particular de evento. Cuando se produce ese evento, se invoca la devolución de llamada.
Las devoluciones de llamadas se utilizan a menudo para programar la interfaz gráfica de usuario (GUI) de un programa que se ejecuta en un sistema de ventanas . La aplicación proporciona una referencia a una función de devolución de llamada personalizada para que la llame el sistema de ventanas. El sistema de ventanas llama a esta función para notificar a la aplicación eventos como clics del mouse y pulsaciones de teclas .
Se puede utilizar una devolución de llamada para implementar el procesamiento asincrónico. El autor de la llamada solicita una acción y proporciona una devolución de llamada que se ejecutará cuando la acción se complete, lo que puede ocurrir mucho después de que se realice la solicitud.
Se puede utilizar una devolución de llamada para implementar el polimorfismo . En el siguiente pseudocódigo, SayHi
puede tomar WriteStatus
o WriteError
.
def WriteStatus ( string mensaje ): Write ( stdout , mensaje ) def WriteError ( string mensaje ): Write ( stderr , mensaje ) def SayHi ( escribir ): write ( "Hola mundo" )
Se puede utilizar una devolución de llamada para implementar un comportamiento condicional. En el siguiente pseudocódigo, si el registro está habilitado, Log
se llama a la devolución de llamada getMessage
y se escribe el resultado. Pero, si el registro no está habilitado, getMessage
no se llama, lo que ahorra el costo de tiempo de ejecución.
def Log ( getMessage ): si isLoggingEnabled : mensaje = getMessage (); WriteLine ( mensaje );
La tecnología de devolución de llamada se implementa de forma diferente según el lenguaje de programación .
En lenguajes de ensamblaje , C , C++ , Pascal , Modula2 y otros, una función de devolución de llamada se almacena internamente como un puntero de función . El uso del mismo almacenamiento permite que distintos lenguajes compartan devoluciones de llamada directamente sin una capa de interoperabilidad en tiempo de diseño o de ejecución . Por ejemplo, la API de Windows es accesible a través de múltiples lenguajes, compiladores y ensambladores.
C++ también permite que los objetos proporcionen una implementación de la operación de llamada a una función. La biblioteca de plantillas estándar acepta estos objetos (llamados functores ) como parámetros.
Muchos lenguajes dinámicos , como JavaScript , Lua , Python , Perl [1] [2] y PHP , permiten pasar un objeto de función.
Los lenguajes CLI como C# y VB.NET proporcionan una referencia de función encapsulante de tipo seguro conocida como delegado .
Los eventos y los controladores de eventos , tal como se utilizan en los lenguajes .NET, permiten realizar devoluciones de llamadas.
Los lenguajes funcionales generalmente admiten funciones de primera clase , que pueden pasarse como devoluciones de llamadas a otras funciones, almacenarse como datos o devolverse desde funciones.
Muchos lenguajes, incluidos Perl, Python, Ruby , Smalltalk , C++ (11+), C# y VB.NET (nuevas versiones) y la mayoría de los lenguajes funcionales, admiten expresiones lambda , funciones sin nombre con sintaxis en línea, que generalmente actúan como devoluciones de llamadas.
En algunos lenguajes, incluidos Scheme , ML , JavaScript, Perl, Python, Smalltalk, PHP (desde 5.3.0), [3] C++ (11+), Java (desde 8), [4] y muchos otros, una lambda puede ser un cierre , es decir, puede acceder a variables definidas localmente en el contexto en el que se define la lambda.
En un lenguaje de programación orientado a objetos, como las versiones de Java anteriores a los argumentos con valores de función, el comportamiento de una devolución de llamada se puede lograr pasando un objeto que implementa una interfaz. Los métodos de este objeto son devoluciones de llamada.
En PL/I y ALGOL 60, un procedimiento de devolución de llamada puede necesitar poder acceder a variables locales en bloques contenedores, por lo que se lo llama a través de una variable de entrada que contiene tanto el punto de entrada como la información de contexto. [5]
Las devoluciones de llamadas tienen una amplia variedad de usos, por ejemplo, en la señalización de errores: un programa Unix podría no querer terminar inmediatamente cuando recibe SIGTERM , por lo que para asegurarse de que su terminación se maneja correctamente, registraría la función de limpieza como una devolución de llamada. Las devoluciones de llamadas también se pueden usar para controlar si una función actúa o no: Xlib permite especificar predicados personalizados para determinar si un programa desea manejar un evento.
En el siguiente código C , la función PrintNumber
utiliza el parámetro getNumber
como una función de devolución de llamada de bloqueo. PrintNumber
Se llama a la función con GetAnswerToMostImportantQuestion
la que actúa como una función de devolución de llamada. Cuando se ejecuta, el resultado es: "Valor: 42".
#include <stdio.h> #include <stdlib.h> void ImprimirNumero ( int ( * obtenerNumero )( void )) { int val = obtenerNumero (); printf ( "Valor: %d \n " , val ); } int ObtenerRespuestaALaPreguntaMásImportante ( void ) { return 42 ; } int main ( void ) { ImprimirNumero ( ObtenerRespuestaALaPreguntaMásImportante ); devolver 0 ; }
En C++, se puede utilizar functor además del puntero de función.
En el siguiente código C#Helper.Method
, el método utiliza el parámetro callback
como una función de devolución de llamada de bloqueo. Helper.Method
Se llama con Log
el cual actúa como una función de devolución de llamada. Cuando se ejecuta, se escribe lo siguiente en la consola: "La devolución de llamada fue: Hola mundo".
clase pública MainClass { static void Main ( string [] args ) { Helper helper = new Helper (); helper . Method ( Log ); } void estático Log ( string str ) { Console.WriteLine ( $"La devolución de llamada fue: {str} " ) ; } } clase pública Helper { método público void ( Acción < cadena > devolución de llamada ) { devolución de llamada ( "Hola mundo" ); } }
En el siguiente código Kotlin , la función askAndAnswer
usa el parámetro getAnswer
como una función de devolución de llamada de bloqueo. askAndAnswer
Se llama con getAnswerToMostImportantQuestion
la función que actúa como una función de devolución de llamada. Al ejecutarla, se le indicará al usuario que la respuesta a su pregunta es "42".
fun main () { print ( "Ingrese la pregunta más importante: " ) val question = readLine () askAndAnswer ( question , :: getAnswerToMostImportantQuestion ) } divertido obtenerRespuestaALaPreguntaMásImportante (): Int { return 42 } fun askAndAnswer ( pregunta : String?, obtenerRespuesta : ( ) -> Int ) { println ( "Pregunta: $ pregunta " ) println ( "Respuesta: ${ obtenerRespuesta () } " ) }
En el siguiente código JavaScript , la función calculate
usa el parámetro operate
como una devolución de llamada de bloqueo. calculate
se llama con multiply
y luego con sum
que actúan como funciones de devolución de llamada.
función calcular ( a , b , operar ) { devolver operar ( a , b ); } función multiplicar ( a , b ) { devolver a * b ; } función suma ( a , b ) { devolver a + b ; } // genera 20 alerta ( calcular ( 10 , 2 , multiplicar )); // genera 12 alerta ( calcular ( 10 , 2 , suma ));
El método de colección .each()
de la biblioteca jQuery utiliza la función que se le pasa como devolución de llamada de bloqueo. Llama a la devolución de llamada para cada elemento de la colección. Por ejemplo:
$ ( "li" ) .each ( función ( índice ) { console .log ( índice + ": " + $ ( this ) .text ( )); });
Las devoluciones de llamadas diferidas se utilizan comúnmente para gestionar eventos del usuario, el cliente y los temporizadores. Se pueden encontrar ejemplos en addEventListener
, Ajax y XMLHttpRequest
. [6]
Además de utilizar devoluciones de llamadas en el código fuente de JavaScript, las funciones C que toman una función se admiten a través de js-ctypes. [7]
El siguiente código REBOL / Red demuestra el uso de devolución de llamada.
Rojo [ Título: "Ejemplo de devolución de llamada" ]calcular: func [ num1 [ ¡número! ] num2 [ ¡número! ] función-de-devolución-de-llamada [ ¡función! ] ][ función-de-devolución-de-llamada num1 num2 ]calc-product: func [ num1 [ ¡número! ] num2 [ ¡número! ] ][ num1 * num2 ]calc-sum: func [ num1 [ ¡número! ] num2 [ ¡número! ] ][ num1 + num2 ]; alertas 75, el producto de 5 y 15 formulario de alerta calcular 5 15 :calc-product ; alertas 20, la suma de 5 y 15 formulario de alerta calcular 5 15 :calc-sum
Un ejemplo de interpolación de color que utiliza el motor Roblox que acepta una devolución de llamada .done opcional:
esperar ( 1 ) DT local = esperar () función tween_color ( objeto , color_final , tiempo_desvanecimiento ) local paso_r = color_final.r - objeto.ColorFondo3.r local paso_g = color_final.g - objeto.ColorFondo3.g local paso_b = color_final.b - objeto.ColorFondo3.b local total_pasos = 1 / ( DT * ( 1 / tiempo_desvanecimiento ) ) local completado ; coroutine.wrap ( función ( ) para i = 0 , 1 , DT * ( 1 / tiempo_desvanecimiento ) hacer objeto.FondoColor3 = Color3.new ( objeto.FondoColor3.r + ( paso_r / pasos_totales ) , objeto.FondoColor3.g + ( paso_g / pasos_totales ) , objeto.FondoColor3.b + ( paso_b / pasos_totales ) ) esperar ( ) fin si se completó entonces se completó ( ) fin fin ) ( ) devolver { hecho = función ( devolución_de_llamada ) completado = devolución_de_llamada fin } fin tween_color ( some_object , Color3.new ( 1,0,0 ) , 1 ) .doin ( function ( ) print " ¡ Interpolación de color finalizada ! " end )
En el siguiente código Pythoncalculate
, la función acepta un parámetro operate
que se utiliza como una devolución de llamada de bloqueo. calculate
se llama con square
que actúa como una función de devolución de llamada.
def cuadrado ( val ): devuelve val ** 2 def calcular ( operar , val ): devuelve operar ( val ) # salidas: 25 calcular ( cuadrado , 5 )
En el siguiente código de Juliacalculate
, la función acepta un parámetro operate
que se utiliza como una devolución de llamada de bloqueo. calculate
se llama con square
que actúa como una función de devolución de llamada.
julia> cuadrado ( val ) = val ^ 2 cuadrado (función genérica con 1 método) julia> calcular ( operar , val ) = operar ( val ) calcular (función genérica con 1 método) julia> calcular ( cuadrado , 5 ) 25