Что такое callback в программировании
Перейти к содержимому

Что такое callback в программировании

  • автор:

Практическое руководство. Реализация функций обратного вызова

В приведенных ниже процедуре и примере показано, как, используя вызов неуправляемого кода, можно напечатать из управляемого приложения значение дескриптора для каждого окна на локальном компьютере. В частности, для печати значения дескриптора окна в процедуре и примере используется функция EnumWindows, которая просматривает список окон, и управляемая функция обратного вызова CallBack.

Реализация функции обратного вызова

  1. Прежде чем приступить к реализации, обратите внимание на сигнатуру функции EnumWindows. Функция EnumWindows имеет следующую сигнатуру:
BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam) 

пример

Imports System Imports System.Runtime.InteropServices Public Delegate Function CallBack( _ hwnd As Integer, lParam As Integer) As Boolean Public Class EnumReportApp Declare Function EnumWindows Lib "user32" ( _ x As CallBack, y As Integer) As Integer Public Shared Sub Main() EnumWindows(AddressOf EnumReportApp.Report, 0) End Sub 'Main Public Shared Function Report(hwnd As Integer, lParam As Integer) _ As Boolean Console.Write("Window handle is ") Console.WriteLine(hwnd) Return True End Function 'Report End Class 'EnumReportApp 
using System; using System.Runtime.InteropServices; public delegate bool CallBack(int hwnd, int lParam); public class EnumReportApp < [DllImport("user32")] public static extern int EnumWindows(CallBack x, int y); public static void Main() < CallBack myCallBack = new CallBack(EnumReportApp.Report); EnumWindows(myCallBack, 0); >public static bool Report(int hwnd, int lParam) < Console.Write("Window handle is "); Console.WriteLine(hwnd); return true; >> 
using namespace System; using namespace System::Runtime::InteropServices; // A delegate type. delegate bool CallBack(int hwnd, int lParam); // Managed type with the method to call. ref class EnumReport < // Report the window handle. public: [DllImport("user32")] static int EnumWindows(CallBack^ x, int y); static void Main() < EnumReport^ er = gcnew EnumReport; CallBack^ myCallBack = gcnew CallBack(&EnumReport::Report); EnumWindows(myCallBack, 0); >static bool Report(int hwnd, int lParam) < Console::Write(L"Window handle is "); Console::WriteLine(hwnd); return true; >>; int main()

См. также

  • Функции обратного вызова
  • Вызов функции DLL

Совместная работа с нами на GitHub

Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.

Callback (программирование)

Callback (англ. call — вызов, англ. back — обратный) или фу́нкция обра́тного вы́зова в программировании — передача исполняемого кода в качестве одного из параметров другого кода. Обратный вызов позволяет в функции исполнять код, который задаётся в аргументах при её вызове. Этот код может быть определён в других контекстах программного кода и быть недоступным для прямого вызова из этой функции. Некоторые алгоритмические задачи в качестве своих входных данных имеют не только числа или объекты, но и действия (алгоритмы), которые естественным образом задаются как обратные вызовы.

Применение

Концепция обратного вызова имеет много приложений. Например, некоторые алгоритмы (функции) в качестве подзадачи имеют задачу вычисления хеш-значения от строки. Какую именно функцию использовать для вычисления хеш-значений удобно задавать в аргументах при запуске алгоритма (функции).

Другой пример алгоритма, которому естественно передавать в аргументе функцию, — алгоритм обхода какого-либо хранилища объектов с применением некоторого действия к каждому объекту. Обратный вызов может выступать в роли этого действия (алгоритма).

Техника программирования обратного вызова в языках программирования, подобных языку Си, проста. При вызове основной функции ей просто передаётся указатель на функцию обратного вызова. Классическим примером является функция qsort из библиотеки stdlib. Эта функция позволяет отсортировать массив блоков байт одинаковой длины. В качестве аргументов она получает адрес первого элемента массива, количество блоков в массиве, размер блока байт, и указатель на функцию сравнения двух блоков байт. Эта функция сравнения и есть функция обратного вызова в данном примере:

#include // функция сравнения целых чисел по модулю int compare_abs(const void *a, const void *b)  int a1 = *(int*)a; int b1 = *(int*)b; return abs(a1) - abs(b1); > int main()  int m[10] = 1,-3,5,-100,7,33,44,67,-4, 0>; // сортировка массива m по возрастанию модулей qsort(m, 10, sizeof(int), compare_abs); return 0; > 

Об обратном вызове можно думать как о действии, передаваемом некоторой основной процедуре в качестве аргумента. И это действие может рассматриваться как:

  • подзадача и использоваться для обработки данных внутри этой процедуры;
  • «телефонная связь», используемая для того, чтобы «связываться» с тем, кто вызвал процедуру, при наступлении какого-то события (англ.callback дословно переводится как «звонок обратно»).

Показанный выше пример как раз соответствует первому случаю. Случай, когда обратный вызов используется как «телефонная связь», отражает код, где задаётся функция обработки определённого сигнала:

#include #include volatile sig_atomic_t br = 1; void sig(int signum)  br=0; > int main(int argc, char *argv[])  signal(SIGINT, sig); printf("Press break keyboard key combination to stop the program\n"); while(br); printf("Received SIGINT, exit\n"); return 0; > 

В некоторых языках программирования, таких как Common Lisp, Scheme, Clojure, Javascript, Perl, Python, Ruby и других, есть возможность конструировать анонимные (не именованные) функции и функции-замыкания прямо в выражении вызова основной функции, и эта возможность широко используется.

В технологии AJAX при выполнении асинхронного запроса к серверу необходимо указывать функцию обратного вызова, которая будет вызвана, как только придёт ответ на запрос. Часто эту функцию определяют «прямо на месте», не давая ей никакого определённого имени:

new Ajax.Request('http://example.com/do_it',  method: 'post', onSuccess: function(transport)  // функция, вызываемая window.alert("Done!"); // при успешном выполнении запроса >, // onFailure: function()  // функция, вызываемая window.alert("Error!"); // при ошибке выполнения запроса > >); 

Функция обратного вызова используется также в шаблоне проектирования «Наблюдатель» (Observer). Так, например, используя библиотеку Prototype, можно создать «наблюдателя», который следит за нажатиями на элемент с идентификатором «my_button» и при получении события пишет сообщение внутрь элемента «message_box» :

Event.observe ($("my_button"), 'click', function()  $("message_box").innerHTML = "Вы нажали на кнопку!" >); 

Функция обратного вызова является альтернативой полиморфизму функций, а именно, позволяет создавать функции более общего назначения вместо того, чтобы создавать серию функций, одинаковых по структуре, но отличающихся лишь в отдельных местах исполняемыми подзадачами. Функции, принимающие в качестве аргументов другие функции или возвращающие функции в качестве результата, называют функциями высшего порядка. Техника обратного вызова играет важную роль для достижения повторного использования кода.

Зачем использовать функции обратного вызова

Для лучшего понимания причин использования обратного вызова рассмотрим простую задачу выполнения следующих операций над списком чисел: напечатать все числа, возвести все числа в квадрат, увеличить все числа на 1, обнулить все элементы. Ясно, что алгоритмы выполнения этих четырёх операций схожи — это цикл обхода всех элементов списка с некоторым действием в теле цикла, применяемый к каждому элементу. Это несложный код, и в принципе можно написать его 4 раза. Но давайте рассмотрим более сложный случай, когда список у нас хранится не в памяти, а на диске, и со списком могут работать несколько процессов одновременно и необходимо решать проблемы синхронизации доступа к элементам (несколько процессов могут выполнять разные задачи — удаления некоторых элементов из списка, добавления новых, изменение существующих элементов в списке). В этом случае задача обхода всех элементов списка будет довольно сложным кодом, который не хотелось бы копировать несколько раз. Правильнее создать функцию общего назначения для обхода элементов списка и дать возможность программистам абстрагироваться от того, как именно устроен алгоритм обхода и писать лишь функцию обратного вызова для обработки отдельного элемента списка.

Структурирование ПО

Структурирование программного обеспечения через функции обратного вызова — очень удобный и широко используемый подход, так как при этом поведение программы с неизменным (в том числе закрытым) кодом можно изменять в очень широких пределах. Это реализуется двумя путями — или «альтернативной реализацией» какой-либо функции, или «добавлением в цепочку вызовов» ещё одной функции.

Как правило, разработчик реализует через обратные вызовы не всю функциональность программы, а лишь ту, которую предполагается расширять или изменять плагинами. Для подключения плагинов предоставляется специальная процедура, которая и заменяет «стандартные» обратные функции от разработчика на альтернативные из плагина.

Самым известным примером такого подхода является операционная система Microsoft Windows, где функции обратного вызова именуются «handler» («обработчик»), и существует возможность вставить дополнительную процедуру между любыми двумя стандартными. Этот подход называется «перехват событий» и используется, например: антивирусами для проверки файлов, к которым производится обращение; вирусами для считывания вводимых с клавиатуры символов; сетевыми фильтрами для сбора статистики и блокирования пакетов.

В современных Unix и Linux системах существует возможность динамической загрузки и выгрузки модулей ядра, работа которых также основана на функциях обратного вызова. При этом существует модуль (расширение ядра) FUSE, который, в свою очередь, предоставляет возможность обычным пользовательским программам обслуживать (перехватывать) запросы к виртуальным файловым системам.

В программном обеспечении иногда встречается декомпозиция программ, полностью основанная на функциях обратного вызова, что несколько ухудшает читаемость кода, но даёт максимальные возможности для плагинов. Пример такого продукта — DokuWiki.

Достоинства и недостатки

  • Возможность динамического изменения функциональности (подключения и отключения плагинов/модулей при работе программы).
  • Возможность неограниченного количества вариантов вызываемой функции без изменения базового (в данном контексте) кода.
  • Возможность вставки вызываемой функции не только для альтернативного поведения, но и в качестве ещё одной (промежуточной) подпрограммы — обычно для отслеживания операций или изменения параметров для следующей (вызываемой) функции. Таких независимых «дополнительных звеньев» в цепочке вызовов может быть сколько угодно.
  • Поддержка функций обратного вызова в большинстве современных языков программирования общего назначения.
  • Уменьшение производительности, связанной с дополнительными вызовами «обратной функции» — прямо пропорционально «стоимости вызова функции» в среде выполнения и количеству дополнительных вызовов при работе программы.
  • Ухудшение читаемости исходного кода — для понимания алгоритма программы необходимо отслеживать всю цепочку вызовов.

См. также

  • Сигналы (программирование)
  • Binding (программирование)
  • Алгоритм обхода двоичного дерева
  • Функция высшего порядка
  • libsigc++, библиотека обратных вызовов для языка Си++
  • libevent

Ссылки

  • callback в Win32 (рус.)
  • Style Case Study #2: Generic Callbacks (англ.)
  • C++ Callback Solution (англ.)
  • Basic Instincts: Implementing Callback Notifications Using Delegates (англ.)
  • Implement Script Callback Framework in ASP.NET (англ.)
  • Реализация функций обратного вызова в Java (англ.)
  • Реализация функций высшего порядка в С# (рус.)
  • Проверить достоверность указанной в статье информации.
  • Проставив сноски, внести более точные указания на источники.
  • Концепции языков программирования
  • Управление потоком

Wikimedia Foundation . 2010 .

Понимание callback-функций (колбеков)

Callback-функции чрезвычайно важны в языке Javascript. Они есть практически повсюду. Но, несмотря на имеющийся опыт программирования на C/Java, с ними у меня возникли трудности (как и с самой идеей асинхронного программирования), и я решил в этом разобраться. Странно, но я не смог найти в интернете хороших вводных статей о callback-функциях — в основном попадались отрывки документации по функциям call() и apply() или короткие кусочки кода, демонстрирующие их использование, и вот, набив шишек в поисках истины, я решил написать введение в callback-функции самостоятельно.

Функции — это объекты

Чтобы понимать callback-функции, нужно понимать обычные функции. Это может показаться банальностью, но функции в Javascript’е — немного странные штуки.

Функции в Javascript’е — на самом деле объекты. А именно, объекты класса Function , создаваемые конструктором Function . В объекте Function содержится строка с JS-кодом данной функции. Если вы перешли с языка C или Java, это может показаться странным (как код может быть строкой?!), но, вообще говоря, в Javascript’е такое сплошь и рядом. Различие между кодом и данными иногда размывается.

// можно создать функцию, передав в конструктор Function строку с кодом var func_multiply = new Function("arg1", "arg2", "return arg1 * arg2;"); func_multiply(5, 10); // => 50 

Преимущество концепции «функция-как-объект» заключается в том, что код можно передавать в другую функцию точно так же, как обычную переменную или объект (потому что в буквальном понимании код — всего лишь объект).

Передача функции как callback-функции

Передавать функцию в качестве аргумента просто.

// определяем нашу функцию с аргументом callback function some_function(arg1, arg2, callback) < // переменная, генерирующая случайное число в интервале между arg1 и arg2 var my_number = Math.ceil(Math.random() * (arg1 - arg2) + arg2); // теперь всё готово и мы вызываем callback, куда передаём наш результат callback(my_number); >// вызываем функцию some_function(5, 15, function (num) < // эта анонимная функция выполнится после вызова callback-функции console.log("callback called! " + num); >); 

Может показаться глупостью создавать такой перемудрённый код, когда можно вернуть значение нормальным способом, но существуют ситуации, в которых это непрактично и callback-функции необходимы.

Не загромождайте выход

Традиционно функции в ходе выполнения принимают на вход аргументы и возвращают значение, используя выражение return (в идеальном случае единственное выражение return в конце функции: одна точка входа и одна точка выхода). Это имеет смысл. Функции — это, в сущности, маршруты между вводом и выводом.

Javascript даёт возможность делать всё немного по-другому. Вместо того чтобы дожидаться, пока функция закончит выполняться и вернёт значение, мы можем использовать callback-функции, чтобы получить его асинхронно. Это полезно для случаев, когда требуется много времени для завершения, например, при AJAX-запросах, ведь мы не можем приостановить браузер. Мы можем продолжить заниматься другими делами в ожидании вызова колбека. Фактически, очень часто от нас требуется (или, точнее, нам настоятельно рекомендуется) делать всё асинхронно в Javascript’е.

Вот более детальный пример, в котором используется AJAX для загрузки XML-файла и используется функция call() для вызова callback-функции в контексте запрошенного объекта (это значит, что когда мы укажем ключевое слово this внутри callback-функции, оно будет ссылаться на запрошенный объект):

function some_function2(url, callback) < var httpRequest; // создаём наш XMLHttpRequest-объект if (window.XMLHttpRequest) < httpRequest = new XMLHttpRequest(); >else if (window.ActiveXObject) < // для дурацкого Internet Explorer'а httpRequest = new ActiveXObject("Microsoft.XMLHTTP"); >httpRequest.onreadystatechange = function () < // встраиваем функцию проверки статуса нашего запроса // это вызывается при каждом изменении статуса if (httpRequest.readyState === 4 && httpRequest.status === 200) < callback.call(httpRequest.responseXML); // вызываем колбек >>; httpRequest.open('GET', url); httpRequest.send(); > // вызываем функцию some_function2("text.xml", function () < console.log(this); >); console.log("это выполнится до вышеуказанного колбека"); 

В этом примере мы создаём объект httpRequest и загружаем файл XML. Типичная парадигма возвращения значения в конце функции тут больше не работает. Наш запрос обрабатывается асинхронно, а это означает, что мы начинаем запрос и говорим ему вызвать нашу функцию, как только он закончится.

Мы используем здесь две анонимных функции. Важно помнить, что нам бы не составило труда использовать и именованные функции, но во имя лаконичности мы сделали их вложенными. Первая анонимная функция выполняется всякий раз при изменении статуса в нашем объекте httpRequest. Мы игнорируем это до тех пор, пока состояние не будет равно 4 (т.е. запрос выполнен) и статус будет равен 200 (т.е. запрос выполнен успешно). В реальном мире вам бы захотелось проверить, не провален ли запрос, но мы предполагаем, что файл существует и может быть загружен браузером. Эта анонимная функция связана с httpRequest.onreadystatechange, так что она выполняется не сразу, а вызывается каждый раз при изменении состояния в нашем запросе.

Когда мы наконец завершаем наш AJAX-запрос, мы не просто запускаем callback-функцию, мы используем для этого функцию call() . Это ещё один способ вызова callback-функции. Метод, использованный нами до этого — простой запуск функции здесь сработал бы хорошо, но я подумал, что стоит продемонстрировать и использование функции call() . Как вариант, можно использовать функцию apply() (обсуждение разницы между ней и call() выходят за рамки этой статьи, скажу лишь, что это затрагивает способ передачи аргументов функции).

В использовании call() замечательно то, что мы сами устанавливаем контекст, в котором выполняется функция. Это означает, что когда мы используем ключевое слово this внутри нашей callback-функции, оно ссылается на то, что мы передаём первым аргументом в call() . В данном примере, когда мы ссылались на this внутри нашей анонимной функции, мы ссылались на responseXML, полученный в результате AJAX-запроса.

Наконец, второе выражение console.log выполнится первым, потому что callback-функция не выполняется до тех пор, пока не закончен запрос, и пока это произойдёт, последующие части кода продолжают спокойно выполняться.

Обёртывай это

Надеюсь, теперь вы стали понимать callback-функции достаточно хорошо, чтобы начать их использовать в своём собственном коде. Мне всё ещё трудно структурировать код, который зиждется на callback-функциях (в конце концов он становится похож на спагетти… мой разум слишком привык к обычному структурному программированию), но они — очень мощный инструмент и одна из интереснейших частей языка Javascript.

Что такое callback-функция в JavaScript?

Что такое callback-функция в JavaScript? главное изображение

Простыми словами: коллбэк — это функция, которая должна быть выполнена после того, как другая функция завершила выполнение (отсюда и название: callback — функция обратного вызова).

Чуть сложнее: В JavaScript функции — это объекты. Поэтому функции могут принимать другие функции в качестве аргументов, а также возвращать функции в качестве результата. Функции, которые это умеют, называются функциями высшего порядка. А любая функция, которая передается как аргумент, называется callback-функцией.

Зачем нужны коллбэки?

По одной простой причине: JavaScript — это событийно-ориентированный язык. Поэтому вместо того, чтобы ждать ответа для дальнейшего выполнения программы, JavaScript продолжит выполнение, одновременно ожидая других событий. Давайте разберем простой пример:

const first = () =>  console.log(1); >; const second = () =>  console.log(2); >; first(); second(); 

Как вы и ожидаете, функция first выполнится первой, а функция second уже после нее. Поэтому в консоли будет выведен следующий результат:

Пока что все понятно. Но что, если функция first содержит некий код, который не может выполниться немедленно? К примеру, работа с API, где мы отправляем запрос и должны ждать ответа. Чтобы смоделировать такую ситуацию, мы используем функцию setTimeout , которая вызывает функцию после заданного временного промежутка. Мы отсрочим выполнение функции на 500 миллисекунд, как будто бы это запрос к некому API. Теперь код будет выглядеть так:

const first = () =>  // Как будто бы запрос к API setTimeout(() =>  console.log(1); >, 500 ); >; const second = () =>  console.log(2); >; first(); second(); 

Неважно, понимаете ли вы сейчас, как работает setTimeout() . Основная идея — теперь мы отложили исполнение команды console.log(1) на 500 миллисекунд. И что теперь выведет наша программа?

Хотя мы по-прежнему вызываем функцию first первой, ее вывод появился вторым, после вывода функции second . Но JavaScript не нарушает порядок вызова функций, он просто не дожидается ответа от функции first , а сразу двигается дальше — к функции second .

Поэтому нельзя просто вызывать функции в нужном порядке и надеяться, что они в любом случае выполнятся в том же порядке. Коллбэки же позволяют нам быть уверенными в том, что определенный код не начнет исполнение до того момента, пока другой код не завершит исполнение.

Фронтенд-разработчик — с нуля до трудоустройства за 10 месяцев

  • Постоянная поддержка от наставника и учебного центра
  • Помощь с трудоустройством
  • Готовое портфолио к концу обучения
  • Практика с первого урока

Вы получите именно те инструменты и навыки, которые позволят вам найти работу

Создаем коллбэк

Во-первых, откройте консоль разработчика в Google Chrome (Windows: Ctrl + Shift + J)(Mac: Cmd + Option + J), либо свой IDE, либо просто Repl.it , и введите в консоли следующую функцию:

const doHomework = (subject) =>  alert(`Starting my $subject> homework.`); >; 

Мы создали функцию doHomework . Наша функция принимает одну переменную — название предмета, которым мы будем заниматься. Вызовите функцию, набрав следующий текст в консоли:

doHomework('math'); // Выводит алерт: Starting my math homework. 

Теперь давайте добавим в определение функции еще один параметр, это и будет наш коллбэк. Затем вызовем ее, определив функцию-callback в качестве аргумента:

const doHomework = (subject, callback) =>  alert(`Starting my $subject> homework.`); callback(); >; doHomework('math', () =>  alert('Finished my homework'); >); 

Если вы введете этот код в консоли, вы получите два алерта один за другим, в первом будет сообщение о том, что выполнение домашнего задания началось (Starting my math homework.), а во втором — что вы закончили выполнять задание (Finished my homework).

Однако коллбэки не обязательно должны быть определены при вызове функции. Они могут быть определены и в другом месте кода, например, так:

const doHomework = (subject, callback) =>  alert(`Starting my $subject> homework.`); callback(); >; const alertFinished = () =>  alert('Finished my homework'); >; doHomework('math', alertFinished); 

Таким образом, результат выполнения этого кода такой же, как и в предыдущем примере, однако сам код немного другой. Как вы видите, мы передали функцию alertFinished как аргумент в функцию doHomework при ее вызове.

Перепишем пример вызова функции с setTimeout для последовательного выполнения функций:

const first = (callback) =>  // Как будто бы запрос к API setTimeout(() =>  console.log(1); callback(); >, 500 ); >; const second = () =>  console.log(2); >; first(second); // 1 // 2 

Пример из реальной жизни

На прошлой неделе я опубликовал статью «Создаем бота для Твиттера в 38 строк кода» . Этот код работает благодаря API Твиттера. И когда мы делаем запрос к API, мы должны дождаться ответа до того, как начнем выполнять с этим ответом какие-то действия. Это прекрасный пример того, как в реальной жизни выглядит коллбэк. Вот как выглядит сам запрос:

T.get('search/tweets', params, (err, data, response) =>  if (!err)  // Происходит какая-то магия > else  console.log(err); > >); 

T.get просто значит, что мы выполняем get запрос к API Твиттера. В запросе три параметра: ‘search/tweets’ – это адрес (роут) запроса, params – наши параметры поиска и в конце передается анонимная функция-callback.

Коллбэк здесь нужен, потому что нам нужно дождаться ответа от сервера до того, как приступим к дальнейшему выполнению кода. Мы не знаем, успешным будет наш запрос или нет, поэтому после отправки параметров поиска на search/tweets через get-запрос, мы просто ждем. Как только Твиттер ответит, выполнится наша callback-функция. Твиттер отправит нам в качестве ответа или объект err (error – ошибка), или объект response. В коллбэке мы можем через if() проверить, был ли запрос успешным или нет, и затем действовать соответственно.

Профессия «Фронтенд-разработчик»

  • Изучите востребованные JavaScript и TypeScript
  • Научитесь создавать пользовательские интерфейсы сайтов и приложений
  • Освойте самый популярный фреймворк JavaScript — React
  • Познакомьтесь с языками веб-разработки HTML и CSS

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *