В компьютерном программировании обратный вызов — это функция , которая хранится в виде данных ( ссылки ) и предназначена для вызова другой функцией — часто обратно на исходный уровень абстракции .
Функция, которая принимает параметр обратного вызова , может быть разработана для обратного вызова перед возвратом к вызывающей стороне, что известно как синхронная или блокирующая . Функция, которая принимает обратный вызов, может быть разработана для сохранения обратного вызова, чтобы ее можно было вызвать обратно после возврата, что известно как асинхронная , неблокирующая или отложенная .
Языки программирования поддерживают обратные вызовы различными способами, такими как указатели на функции , лямбда-выражения и блоки .
Чтобы облегчить понимание концепции, ниже приведена аналогия из реальной жизни.
Клиент посещает магазин, чтобы сделать заказ. Это как первый звонок.
Клиент передает клерку список товаров, чек на покрытие их стоимости и инструкции по доставке. Это параметры первого звонка, включая обратный звонок, который является инструкциями по доставке. Подразумевается, что чек будет обналичен и что инструкции будут выполнены.
Когда сотрудники могут, они доставляют товары в соответствии с инструкциями, что является своего рода ответным звонком.
Примечательно, что доставка не обязательно должна быть выполнена клерком, который принял заказ. Обратный вызов не обязательно должен быть вызван функцией, которая приняла обратный вызов как параметр.
Кроме того, доставка не обязательно должна быть сделана непосредственно клиенту. Обратный вызов не обязательно должен быть вызовом вызывающей функции. Фактически, функция обычно не будет передавать себя как обратный вызов. Некоторые считают использование back вводящим в заблуждение, поскольку вызов (обычно) не является обратным вызовом исходному вызывающему, как это происходит при телефонном звонке .
Блокирующий обратный вызов выполняется в контексте выполнения функции, которая передает обратный вызов. Отложенный обратный вызов может выполняться в другом контексте, например, во время прерывания или из потока . Таким образом, отложенный обратный вызов может использоваться для синхронизации и делегирования работы другому потоку.
Обратный вызов может использоваться для обработки событий. Часто потребляющий код регистрирует обратный вызов для определенного типа события. Когда это событие происходит, вызывается обратный вызов.
Обратные вызовы часто используются для программирования графического пользовательского интерфейса (GUI) программы, которая работает в оконной системе . Приложение предоставляет ссылку на пользовательскую функцию обратного вызова для вызова оконной системой. Оконная система вызывает эту функцию, чтобы уведомить приложение о таких событиях, как щелчки мыши и нажатия клавиш .
Обратный вызов может использоваться для реализации асинхронной обработки. Вызывающий объект запрашивает действие и предоставляет обратный вызов, который будет вызван после завершения действия, что может произойти намного позже запроса.
Обратный вызов может быть использован для реализации полиморфизма . В следующем псевдокоде SayHi
может принимать либо , WriteStatus
либо WriteError
.
def WriteStatus ( string message ): Write ( stdout , message ) def WriteError ( string message ): Write ( stderr , message ) def SayHi ( write ): write ( "Привет, мир" )
Обратный вызов может использоваться для реализации условного поведения. В следующем псевдокоде, если ведение журнала включено, Log
вызывает обратный вызов, getMessage
и записывает результат. Но если ведение журнала не включено, то getMessage
не вызывается, что экономит время выполнения.
def Log ( getMessage ): если isLoggingEnabled : message = getMessage (); WriteLine ( message );
Технология обратного вызова реализуется по-разному в зависимости от языка программирования .
В ассемблере , C , C++ , Pascal , Modula2 и других языках функция обратного вызова хранится внутри как указатель функции . Использование одного и того же хранилища позволяет разным языкам напрямую совместно использовать обратные вызовы без слоя взаимодействия во время разработки или выполнения . Например, API Windows доступен через несколько языков, компиляторов и ассемблеров.
C++ также позволяет объектам предоставлять реализацию операции вызова функции. Стандартная библиотека шаблонов принимает эти объекты (называемые функторами ) в качестве параметров.
Многие динамические языки , такие как JavaScript , Lua , Python , Perl [1] [2] и PHP , позволяют передавать объект функции.
Языки CLI, такие как C# и VB.NET, предоставляют типобезопасную инкапсулирующую ссылку на функцию, известную как делегат .
События и обработчики событий , используемые в языках .NET, обеспечивают обратные вызовы.
Функциональные языки обычно поддерживают функции первого класса , которые могут передаваться как обратные вызовы другим функциям, сохраняться как данные или возвращаться из функций.
Многие языки, включая Perl, Python, Ruby , Smalltalk , C++ (11+), C# и VB.NET (новые версии), а также большинство функциональных языков поддерживают лямбда-выражения — неименованные функции со встроенным синтаксисом, которые обычно действуют как обратные вызовы.
В некоторых языках, включая Scheme , ML , JavaScript, Perl, Python, Smalltalk, PHP (начиная с версии 5.3.0), [3] C++ (11+), Java (начиная с версии 8) [4] и многих других, лямбда-выражение может быть замыканием , т. е. может получать доступ к переменным, локально определенным в контексте, в котором определена лямбда-выражение.
В объектно-ориентированном языке программирования , таком как версии Java до появления аргументов с функциональным значением, поведение обратного вызова может быть достигнуто путем передачи объекта, реализующего интерфейс. Методы этого объекта являются обратными вызовами.
В PL/I и ALGOL 60 процедура обратного вызова может потребовать доступа к локальным переменным в содержащих ее блоках, поэтому она вызывается через входную переменную, содержащую как точку входа, так и контекстную информацию. [5]
Обратные вызовы имеют широкий спектр применения, например, для сигнализации об ошибках: программа Unix может не захотеть немедленно завершиться, получив SIGTERM , поэтому, чтобы убедиться, что ее завершение обрабатывается правильно, она зарегистрирует функцию очистки как обратный вызов. Обратные вызовы также могут использоваться для управления тем, действует ли функция или нет: Xlib позволяет указывать пользовательские предикаты, чтобы определить, хочет ли программа обрабатывать событие.
В следующем коде C функция PrintNumber
использует параметр getNumber
как блокирующий обратный вызов. PrintNumber
вызывается с помощью GetAnswerToMostImportantQuestion
которого действует как функция обратного вызова. При запуске вывод: "Значение: 42".
#include <stdio.h> #include <stdlib.h> void PrintNumber ( int ( * getNumber )( void )) { int val = getNumber (); printf ( "Значение: %d \n " , val ); } int ПолучитьОтветНаСамыйВажныйВопрос ( void ) { return 42 ; } int main ( void ) { PrintNumber ( GetAnswerToMostImportantQuestion ); return 0 ; }
В C++ в дополнение к указателю на функцию можно использовать функтор .
В следующем коде C# метод Helper.Method
использует параметр callback
как блокирующий обратный вызов. Helper.Method
вызывается с помощью Log
которого действует как функция обратного вызова. При запуске в консоль выводится следующее: "Callback was: Hello world".
public class MainClass { static void Main ( string [] args ) { Helper helper = new Helper (); helper . Method ( Log ); } static void Log ( string str ) { Console . WriteLine ( $"Обратный вызов был: {str}" ); } } public class Helper { public void Method ( Action < string > callback ) { callback ( "Привет, мир" ); } }
В следующем коде Kotlin функция askAndAnswer
использует параметр getAnswer
как блокирующий обратный вызов. askAndAnswer
вызывается с помощью getAnswerToMostImportantQuestion
, который действует как функция обратного вызова. Запуск этого сообщит пользователю, что ответ на его вопрос — «42».
fun main () { print ( "Введите самый важный вопрос: " ) val question = readLine () askAndAnswer ( question , :: getAnswerToMostImportantQuestion ) } весело получитьОтветНаСамыйВажныйВопрос (): Int { return 42 } fun askAndAnswer ( question : String? , getAnswer : () -> Int ) { println ( "Вопрос: $ question " ) println ( "Ответ: ${ getAnswer () } " ) }
В следующем коде JavaScript функция calculate
использует параметр operate
в качестве блокирующего обратного вызова. calculate
вызывается с помощью multiply
и затем с помощью sum
, которые действуют как функции обратного вызова.
function calculate ( a , b , opera ) { return opera ( a , b ); } function multiply ( a , b ) { return a * b ; } function sum ( a , b ) { return a + b ; } // выводит 20 alert ( calculate ( 10 , 2 , multiply )); // выводит 12 alert ( calculate ( 10 , 2 , sum ));
Метод collection .each()
библиотеки jQuery использует переданную ему функцию как блокирующий обратный вызов. Он вызывает обратный вызов для каждого элемента коллекции. Например:
$ ( "li" ). each ( function ( index ) { console.log ( index + " : " + $ ( this ) .text ()); }) ;
Отложенные обратные вызовы обычно используются для обработки событий от пользователя, клиента и таймеров. Примеры можно найти в addEventListener
, Ajax и XMLHttpRequest
. [6]
Помимо использования обратных вызовов в исходном коде JavaScript, функции C, которые принимают функцию, поддерживаются через js-ctypes. [7]
Следующий код REBOL / Red демонстрирует использование обратного вызова.
Красный [ Заголовок: "Пример обратного вызова" ]вычислить: функция [ num1 [ число! ] num2 [ число! ] функция-обратного вызова [ функция! ] ][ функция-обратного вызова num1 num2 ]calc-product: func [ num1 [ число! ] num2 [ число! ] ][ num1 * num2 ]calc-sum: func [ num1 [ число! ] num2 [ число! ] ][ num1 + num2 ]; оповещения 75, произведение 5 и 15 форма оповещения вычислить 5 15 :calc-product ; оповещения 20, сумма 5 и 15 оповещения формы расчета 5 15 :calc-sum
Пример цветового промежуточного изображения с использованием движка Roblox , который принимает необязательный обратный вызов .done:
ждать ( 1 ) локальный DT = ждать ()function tween_color ( object , finish_color , fade_time ) local step_r = finish_color.r - object.FoundColor3.r local step_g = finish_color.g - object.FoundColor3.g local step_b = finish_color.b - object.FoundColor3.b local total_steps = 1 / ( DT * ( 1 / fade_time ) ) local performed ; coroutine.wrap ( function ( ) for i = 0 , 1 , DT * ( 1 / fade_time ) do object.BackgroundColor3 = Color3.new ( object.BackgroundColor3.r + ( step_r / total_steps ) , object.BackgroundColor3.g + ( step_g / total_steps ) , object.BackgroundColor3.b + ( step_b / total_steps ) ) wait ( ) end if complete then complete ( ) end end ) ( ) return { done = function ( callback ) performed = callback end } end tween_color ( some_object , Color3.new ( 1,0,0 ) , 1 ) .done ( function () print " Цветовая анимация завершена ! " end )
В следующем коде Python функция calculate
принимает параметр operate
, который используется как блокирующий обратный вызов. calculate
вызывается с помощью square
которого действует как функция обратного вызова.
def square ( val ): return val ** 2 def calculate ( opera , val ): return opera ( val ) # выходы: 25 calculate ( square , 5 )
В следующем коде Julia функция calculate
принимает параметр operate
, который используется как блокирующий обратный вызов. calculate
вызывается с помощью square
которого действует как функция обратного вызова.
julia> square ( val ) = val ^ 2 square (общая функция с 1 методом) julia> calculate ( opera , val ) = opera ( val ) calculate (общая функция с 1 методом) julia> calculate ( square , 5 ) 25