Что такое функция обратного вызова? - PullRequest
630 голосов
/ 05 мая 2009

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

Ответы [ 21 ]

615 голосов
/ 26 сентября 2011

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

Функция обратного вызова - это функция, которая:

  • доступно для другой функции и
  • вызывается после первой функции, если эта первая функция завершает

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

Возможно, лучшим именем будет "вызов после" функции.

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

псевдокод:

// A function which accepts another function as an argument
// (and will automatically invoke that function when it completes - note that there is no explicit call to callbackFunction)
funct printANumber(int number, funct callbackFunction) {
    printout("The number you provided is: " + number);
}

// a function which we will use in a driver function as a callback function
funct printFinishMessage() {
    printout("I have finished printing numbers.");
}

// Driver method
funct event() {
   printANumber(6, printFinishMessage);
}

Результат, если вы вызвали event ():

The number you provided is: 6
I have finished printing numbers.

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

Обратные вызовы являются так называемыми из-за их использования с языками указателей. Если вы не используете один из них, не работайте над именем «обратный вызов». Просто поймите, что это просто имя для описания метода, который предоставляется в качестве аргумента для другого метода, например, когда вызывается родительский метод (любое условие, такое как нажатие кнопки, отметка таймера и т. Д.) И его тело завершается, затем вызывается функция обратного вызова.

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

206 голосов
/ 04 мая 2014

Непрозрачное определение

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

Придуманный пример

Зачем тебе это делать? Допустим, есть служба, которую нужно вызвать. Если услуга возвращается немедленно, вы просто:

  1. Назовите это
  2. Дождаться результата
  3. Продолжить, как только появится результат

Например, предположим, что службой была функция factorial. Если вам нужно значение 5!, вы вызовете factorial(5), и произойдут следующие шаги:

  1. Текущее место выполнения сохраняется (в стеке, но это не важно)

  2. Исполнение передано factorial

  3. Когда factorial завершается, результат помещается туда, где вы можете его получить

  4. Казнь возвращается туда, где она была в [1]

Теперь предположим, что factorial заняло очень много времени, потому что вы даете ему огромные цифры и он должен работать на каком-то суперкомпьютерном кластере. Допустим, вы ожидаете, что это займет 5 минут, чтобы вернуть ваш результат. Вы могли бы:

  1. Сохраняйте свой дизайн и запускайте программу ночью, когда вы спите, чтобы не смотреть на экран половину времени

  2. Создайте свою программу для других целей, пока factorial делает свое дело

Если вы выберете второй вариант, обратные вызовы могут работать для вас.

Сквозной дизайн

Чтобы использовать шаблон обратного вызова, вы должны иметь возможность вызвать factorial следующим образом:

factorial(really_big_number, what_to_do_with_the_result)

Второй параметр, what_to_do_with_the_result, - это функция, которую вы отправляете вместе с factorial, в надежде, что factorial вызовет его для результата перед возвратом.

Да, это означает, что factorial должен быть написан для поддержки обратных вызовов.

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

factorial (number, callback, params)
{
    result = number!   // i can make up operators in my pseudocode
    callback (result, params)
}

Теперь, когда factorial разрешает этот шаблон, ваш обратный вызов может выглядеть следующим образом:

logIt (number, logger)
{
    logger.log(number)
}

и ваш звонок на factorial будет

factorial(42, logIt, logger)

Что если вы хотите что-то вернуть из logIt? Ну, вы не можете, потому что factorial не обращает на это внимания.

Ну, почему factorial просто не может вернуть то, что возвращает ваш обратный вызов?

Сделать его неблокирующим

Поскольку выполнение предназначено для передачи обратному вызову после завершения factorial, оно действительно не должно ничего возвращать вызывающей стороне. И в идеале, он каким-то образом запустит свою работу в другом потоке / процессе / машине и сразу же вернется, чтобы вы могли продолжить, возможно, что-то вроде этого:

factorial(param_1, param_2, ...)
{
    new factorial_worker_task(param_1, param_2, ...);
    return;
}

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

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

Так что ты делаешь?

Ответ - оставаться в рамках схемы обратного вызова. Всякий раз, когда вы хотите написать

a = f()
g(a)

и f должны вызываться асинхронно, вместо этого вы будете писать

f(g)

, где g передается как обратный вызов.

Это в корне меняет топологию потока вашей программы и требует некоторого привыкания.

Ваш язык программирования может очень вам помочь, предоставляя вам возможность создавать функции на лету. В приведенном выше коде функция g может быть такой же маленькой, как print (2*a+1). Если ваш язык требует, чтобы вы определили это как отдельную функцию с совершенно ненужным именем и подписью, то ваша жизнь станет неприятной, если вы будете часто использовать этот шаблон.

Если, с другой стороны, ваш язык позволяет вам создавать лямбды, то вы в гораздо лучшей форме. Тогда вы в конечном итоге напишите что-то вроде

f( func(a) { print(2*a+1); })

что намного приятнее.

Как пройти обратный вызов

Как бы вы передали функцию обратного вызова factorial? Ну, вы могли бы сделать это несколькими способами.

  1. Если вызываемая функция выполняется в том же процессе, вы можете передать указатель на функцию

  2. Или, может быть, вы хотите сохранить словарь fn name --> fn ptr в вашей программе, в этом случае вы можете передать имя

  3. Возможно, ваш язык позволяет вам определять функцию на месте, возможно, как лямбда! Внутренне это создает какой-то объект и передает указатель, но вам не нужно об этом беспокоиться.

  4. Возможно, вызываемая вами функция выполняется на совершенно отдельной машине, и вы вызываете ее, используя сетевой протокол, такой как HTTP. Вы можете представить свой обратный вызов как функцию, вызываемую по HTTP, и передать ее URL.

Вы поняли.

Недавний рост обратных вызовов

В эту эру Интернета, в которую мы вступили, службы, к которым мы обращаемся, часто находятся в сети. Мы часто не имеем никакого контроля над этими службами, т. Е. Мы их не писали, мы их не обслуживаем, мы не можем гарантировать, что они работают или как они работают.

Но мы не можем ожидать, что наши программы будут блокироваться, пока мы ожидаем ответа этих служб. Осознавая это, поставщики услуг часто разрабатывают API, используя шаблон обратного вызова.

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

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

90 голосов
/ 05 мая 2009

Обратите внимание, что обратный вызов - это одно слово.

Страница обратного вызова в Википедии объясняет это очень хорошо.

цитата со страницы википедии:

В компьютерном программировании обратный вызов - это ссылка на исполняемый код или фрагмент исполняемого кода, который передается в качестве аргумента другому коду. Это позволяет программному уровню более низкого уровня вызывать подпрограмму (или функцию), определенную на уровне более высокого уровня.

42 голосов
/ 15 сентября 2011

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

40 голосов
/ 05 мая 2009

Функция обратного вызова - это функция, которая должна вызываться при выполнении определенного условия. Вместо немедленного вызова, функция обратного вызова вызывается в определенный момент в будущем.

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

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

33 голосов
/ 20 июля 2012

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

Функция обратного вызова - это функция, которую вы передаете кому-то и позволяете они называют это в какой-то момент времени.

Я думаю, что люди просто читают первое предложение определения вики:

обратный вызов - это ссылка на исполняемый код или фрагмент исполняемый код, который передается в качестве аргумента другому коду.

Я работал со многими API, вижу множество плохих примеров. Многие люди склонны называть указатель функции (ссылка на исполняемый код) или анонимные функции (часть исполняемого кода) «обратным вызовом», если они просто функции, зачем вам другое имя для этого?

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

Это позволяет программному уровню более низкого уровня вызывать подпрограмму (или функция), определенная на уровне более высокого уровня.

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

Я надеюсь, что люди могут перестать использовать это слово в неоднозначном контексте, оно не может помочь людям понять лучше только хуже.

30 голосов
/ 11 января 2014

Обратные вызовы проще всего описать в терминах телефонной системы. Функциональный вызов аналогичен вызову кого-либо по телефону, задает ему вопрос, получает ответ и вешает трубку; добавление обратного вызова изменяет аналогию, так что после того, как вы зададите ей вопрос, вы также дадите ей свое имя и номер, чтобы она могла перезвонить вам с ответом. - Пол Якубик, "Реализации обратного вызова в C ++"

28 голосов
/ 29 февраля 2016

Давайте будем проще. Что такое функция обратного вызова?

Пример по притче и аналогии

У меня есть секретарь. Каждый день я прошу ее: (i) отослать исходящую почту фирмы в почтовом отделении, и после она сделала это, чтобы выполнить: (ii) любую задачу, которую я написал для нее на одной из этих заметок .

Теперь, какова задача на заметке? Задача меняется день ото дня.

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

В итоге:

  1. сначала ей нужно разослать почту и
  2. сразу после того, как сделано, ей нужно распечатать некоторые документы.

Функция обратного вызова - это вторая задача: распечатать эти документы. Потому что это делается ПОСЛЕ того, как почта отбрасывается, а также потому, что ей дается липкая записка с указанием напечатать документ вместе с почтой, которую она должна отправить.

Давайте теперь свяжем это с лексикой программирования

  • Имя метода в этом случае: DropOffMail.
  • И функция обратного вызова: PrintOffDocuments. PrintOffDocuments является функцией обратного вызова, потому что мы хотим, чтобы секретарь делал это, только после запуска DropOffMail.
  • Поэтому я бы "передал: PrintOffDocuments в качестве" аргумента "методу DropOffMail. Это важный момент.

Вот и все. Ничего более. Я надеюсь, что это прояснилось для вас - и если нет, оставьте комментарий, и я сделаю все возможное, чтобы уточнить.

17 голосов
/ 28 октября 2010

Это делает обратные вызовы похожими на операторы возврата в конце методов.

Я не уверен, что они такие.

Я думаю, что обратные вызовы на самом деле являются вызовом функции, вследствие того, что другая функция вызывается и завершается.

Я также думаю, что обратные вызовы предназначены для обращения к исходному вызову, в некотором роде: «Эй! То, о чем вы просили? Я сделал это - просто подумал, что сообщу вам - вернемся к вам».

16 голосов
/ 19 ноября 2013

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...