Как объяснить обратные вызовы на простом английском языке? Чем они отличаются от вызова одной функции из другой функции?

Как объяснить обратные вызовы на простом английском языке? Чем они отличаются от вызова одной функции из другой, принимая некоторый контекст из вызывающей функции? Как объяснить их силу начинающему программисту?

34 ответа

Решение

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

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

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]

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

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

Вы могли бы сказать им, чтобы получить пакет и принести его соседям. Если ваш супруг был таким же глупым, как компьютер, они сидели бы у двери и ждали посылку, пока она не пришла (НЕ ДЕЛАТЬ НИЧЕГО), а затем, как только она пришла, они передали бы ее соседям. Но есть и лучший способ. Скажите вашему супругу, что раз они получают посылку, они должны принести ее соседям. Затем они могут жить нормально, пока не получат посылку.

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

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

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

Здесь мы ждем открытия файла, прежде чем писать в него. Это "блокирует" поток выполнения, и наша программа не может делать ничего другого, что может понадобиться! Что если бы мы могли сделать это вместо этого:

# 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!

Оказывается, мы делаем это с некоторыми языками и фреймворками. Это довольно круто! Проверьте Node.js, чтобы получить некоторую реальную практику с такого рода мышлением.

Как объяснить обратные вызовы на простом английском языке?

Говоря простым языком, функция обратного вызова похожа на Работника, который "перезванивает" своему Менеджеру после завершения Задачи.

Чем они отличаются от вызова одной функции из другой, принимая некоторый контекст из вызывающей функции?

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

Как объяснить их силу начинающему программисту?

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

Некоторый код без обратного вызова:

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)');
}

С обратным вызовом:

Вот пример с обратным вызовом, использующим jQuery getJSON:

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
}

С закрытием:

Часто обратный вызов должен получить доступ state из вызывающей функции, используя closure Это как Работник, которому нужно получить информацию от Менеджера, прежде чем он сможет выполнить свою Задачу. Чтобы создать closure Вы можете встроить функцию, чтобы она видела данные в вызывающем контексте:

/* 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
}

Использование:

// 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); 

закрытие

Наконец, вот определение closure от Дугласа Крокфорда:

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

Смотрите также:

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

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

customizableFunc(customFunctionality)

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

    customizableFucn(customFunctionality) {
      var data = doSomthing();
      customFunctionality(data);
      ...
    }

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

Но это принципиально отличается от использования функций "обратного вызова" для асинхронного программирования, как в AJAX или node.js, или просто для назначения функциональности событиям взаимодействия с пользователем (например, щелчкам мыши). В этом случае вся идея состоит в том, чтобы дождаться наступления условного события, прежде чем выполнять пользовательские функции. Это очевидно в случае взаимодействия с пользователем, но также важно в процессах ввода / вывода (ввода / вывода), которые могут занимать время, например, чтение файлов с диска. Именно здесь термин "обратный вызов" имеет наиболее очевидный смысл. После запуска процесса ввода-вывода (например, запрос на чтение файла с диска или на сервер для возврата данных из http-запроса) асинхронная программа не ждет, пока он завершится. Он может выполнять любые задачи, запланированные далее, и отвечать пользовательскими функциями только после того, как он получит уведомление о том, что прочитанный файл или запрос http завершен (или что он не выполнен) и что данные доступны для пользовательской функции. Это все равно, что позвонить в бизнес по телефону и оставить свой номер "обратного вызова", чтобы они могли звонить вам, когда кто-то готов ответить вам. Это лучше, чем повесить трубку для тех, кто знает, как долго и не может заниматься другими делами.

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

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

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

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

  • Каждый получает одну и ту же пустую форму, но
  • Каждый может написать другой экстренный контактный номер.

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

Пример 1:

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

В этом примере вы не используете карандаш для заполнения бланка на листе бумаги, вы используете функцию set_print_callback(the_callback),

  • Пустая переменная в модуле / коде - это пустая строка,
  • set_print_callback это карандаш,
  • а также the_callback это ваша информация, которую вы заполняете.

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

Пример 2:

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

Программирование в Gui работает практически так же. При нажатии кнопки программа должна выяснить, что делать дальше. Идет и ищет обратный звонок. Этот обратный вызов находится в пустом месте с надписью "Вот что вы делаете, когда нажата кнопка1"

Большинство IDE автоматически заполнят для вас бланк (напишите основной метод), когда вы его попросите (например, button1_clicked). Однако этот бланк может иметь любой метод, который вы чертовски хорошо, пожалуйста. Вы можете вызвать метод run_computations или же butter_the_biscuits до тех пор, пока вы поместите имя этого обратного вызова в надлежащее поле. Вы можете поставить "555-555-1212" на номер службы экстренной помощи пустым. Это не имеет особого смысла, но это допустимо.


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

Всегда лучше начать с примера:).

Предположим, у вас есть два модуля A и B.

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

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

*) Очевидным преимуществом здесь является то, что вы абстрагируете все о модуле A от модуля B. Модуль B не должен заботиться о том, кто / что является модулем A.

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

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

Представьте, что вам нужна функция, которая возвращает 10 в квадрате, поэтому вы пишете функцию:

function tenSquared() {return 10*10;}

Позже вам нужно 9 в квадрате, чтобы вы написали другую функцию:

function nineSquared() {return 9*9;}

В конце концов вы замените все это универсальной функцией:

function square(x) {return x*x;}

То же самое относится и к обратным вызовам. У вас есть функция, которая что-то делает, и когда она завершается, вызывается doA:

function computeA(){
    ...
    doA(result);
}

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

function computeB(){
    ...
    doB(result);
}

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

function compute(callback){
    ...
    callback(result);
}

Тогда вам просто нужно вызвать compute(doA) и compute(doB).

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

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

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

Аналогия с ожиданием прибытия посылки, которая использовалась в других ответах, является хорошим объяснением того и другого. В компьютерной программе вы бы сказали компьютеру ожидать посылку. Обычно он теперь сидел бы там и ждал (и больше ничего не делал), пока посылка не прибудет, возможно, на неопределенный срок, если она никогда не прибудет. Для людей это звучит глупо, но без дальнейших мер это совершенно естественно для компьютера.

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

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

Вот реальный пример из моей собственной жизни.

Когда я закончил работу сегодня в 17:00, в моем списке дел были разные вещи:

  • Позвоните ветеринару, чтобы узнать результаты анализов моей собаки.
  • Выгуливать собаку.
  • Работаю над моими налогами.
  • Помыть посуду.
  • Отвечайте на личные электронные письма.
  • Стирать.

Когда я позвонил ветеринару, мне позвонил администратор. Администратор сказал мне, что мне нужно подождать, пока ветеринар будет доступен, чтобы ветеринар мог объяснить мне результаты теста. Администратор хотела приостановить меня, пока ветеринар не будет готов.

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

Как это связано с программным обеспечением

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

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

Вот как код будет выглядеть без обратного вызова:

      function main() {
  callVet();
  // blocked for 15 minutes
  walkDog();
  doTaxes();
  doDishes();
  answerPeronalEmails();
  doLaundry();
}

А теперь с обратными вызовами:

      function main() {
  callVet(function vetCallback(vetOnThePhoneReadyToSpeakWithMe) {
    talkToVetAboutTestResults(vetOnThePhoneReadyToSpeakWithMe);
  });
  walkDog();
  doTaxes();
  doDishes();
  answerPeronalEmails();
  doLaundry();
}

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

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

      function main() {
  displayNavbar();
  const user = getUser();
  // wait a few seconds for response...
  displayUserDashboard(user);
  displaySidebar();
  displayFooter();
}

А теперь с обратными вызовами:

      function main() {
  displayNavbar();
  getUser(function (user) {
    displayUserDashboard(user);
  });
  displaySidebar();
  displayFooter();
}

Используя обратный вызов, мы теперь можем отображать боковую панель и нижний колонтитул до того, как ответ на запрос ajax вернется к нам. Это аналогично тому, как я говорю секретарю: «Я не хочу ждать 15 минут по телефону. Перезвоните мне, когда ветеринар будет готов поговорить со мной, а пока я продолжу работать над другими вещами. мой список дел ". В реальных жизненных ситуациях вам, вероятно, следует быть немного более изящным, но при написании программного обеспечения вы можете быть настолько грубыми, насколько хотите, с процессором.

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

function1(var1, var2) это обычный способ.

Что если я захочу var2 обрабатываться и затем отправляться в качестве аргумента?function1(var1, function2(var2))

Это один тип обратного вызова - где function2 выполняет некоторый код и возвращает переменную обратно в исходную функцию.

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

Без обратного вызова и других специальных программных ресурсов (таких как многопоточность и т. Д.) Программа представляет собой ровно последовательность инструкций, которые выполняются последовательно одна за другой, и даже с неким "динамическим поведением", определяемым определенными условиями, всеми возможными сценариями должны быть предварительно запрограммированы.

Итак, если нам нужно обеспечить реальное динамическое поведение программы, мы можем использовать обратный вызов. С помощью функции обратного вызова вы можете указывать по параметрам, что программа должна вызывать другую программу, предоставляющую некоторые ранее определенные параметры и может ожидать некоторые результаты (это подпись контракта или операции), поэтому эти результаты могут быть получены / обработаны сторонней программой, которая не ранее не было известно.

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

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

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

Я надеюсь, что это объяснение может быть полезным.

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

В Python, например,

grabDBValue( (lambda x: passValueToGUIWindow(x) ))

grabDBValue Можно написать только для получения значения из базы данных, а затем указать, что на самом деле делать со значением, чтобы оно принимало функцию. Вы не знаете когда или если grabDBValue вернется, но если / когда это произойдет, вы знаете, что вы хотите сделать. Здесь я передаю анонимную функцию (или лямбду), которая отправляет значение в окно GUI. Я мог легко изменить поведение программы, выполнив это:

grabDBValue( (lambda x: passToLogger(x) ))

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

Protip:

В языках с лексической областью действия (таких как Scheme или Perl) вы можете использовать такой трюк:

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 в этом случае будет 6 потому что обратный вызов имеет доступ к переменным, объявленным в лексической среде, в которой он был определен. Лексическая область действия и анонимные обратные вызовы - мощная комбинация, требующая дальнейшего изучения для начинающего программиста.

Обратный вызов - это функция, которая будет вызываться второй функцией. Эта вторая функция не знает заранее, какую функцию она будет вызывать. Таким образом, идентификатор функции обратного вызова хранится где-то или передается второй функции в качестве параметра. Эта "идентичность", в зависимости от языка программирования, может быть адресом обратного вызова, указателем какого-либо другого рода или именем функции. Принцип один и тот же, мы храним или передаем некоторую информацию, которая однозначно идентифицирует функцию.

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

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

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

Давайте представим, что вы дали мне потенциально долгосрочное задание: получить имена первых пяти уникальных людей, с которыми вы столкнетесь. Это может занять несколько дней, если я нахожусь в малонаселенном районе. Вам не очень интересно сидеть на руках, пока я бегаю, и вы говорите: "Когда у вас есть список, позвоните мне в мою камеру и прочитайте его мне. Вот номер".

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

В JavaScript это может выглядеть примерно так:

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);

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

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

Обратный вызов в C с использованием указателя на функцию

В C обратный вызов реализован с использованием указателя функций. Указатель на функцию - как следует из названия, является указателем на функцию.

Например, int (*ptrFunc) ();

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

Вот некоторый код для демонстрации указателя на функцию.

#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;
}

Теперь давайте попробуем понять концепцию Callback в C, используя указатель на функцию.

Полная программа имеет три файла: callback.c, reg_callback.h и 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)();                               
}

Если мы запустим эту программу, вывод будет

Это программа, демонстрирующая обратный вызов функции внутри register_callback внутри my_callback назад внутри основной программы

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

Обратный вызов в Java с использованием интерфейса

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

Позвольте мне продемонстрировать это на примере:

Интерфейс обратного вызова

public interface Callback
{
    public void notify(Result result);
}

Абонент или класс более высокого уровня

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
}
}

Функция Callee или нижнего уровня

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
}
}

Обратный вызов с использованием шаблона EventListener

  • Элемент списка

Этот шаблон используется для уведомления от 0 до n номеров Наблюдателей / Слушателей о том, что определенная задача выполнена

  • Элемент списка

Разница между механизмом обратного вызова и механизмом EventListener/Observer заключается в том, что при обратном вызове вызываемый абонент уведомляет единственного вызывающего абонента, в то время как в Eventlisener/Observer вызываемый абонент может уведомить любого, кто заинтересован в этом событии (уведомление может передаваться некоторым другим частям приложение, которое не вызвало задачу)

Позвольте мне объяснить это на примере.

Интерфейс событий

public interface Events {

public void clickEvent();
public void longClickEvent();
}

Виджет класса

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);
    }
}

Кнопка класса

public class Button extends Widget{
private String mButtonText;
public Button (){
} 
public String getButtonText() {
return mButtonText;
}
public void setButtonText(String buttonText) {
this.mButtonText = buttonText;
}
}

Флажок класса

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;
}
}

Класс деятельности

пакет 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();
    }   
}

Другой класс

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...");
}
}

Основной класс

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);
}
}

Как видно из приведенного выше кода, у нас есть интерфейс с названием events, который в основном перечисляет все события, которые могут произойти для нашего приложения. Класс Widget является базовым классом для всех компонентов пользовательского интерфейса, таких как Button, Checkbox. Эти компоненты пользовательского интерфейса являются объектами, которые фактически получают события из кода платформы. Класс Widget реализует интерфейс Events, а также имеет два вложенных интерфейса: OnClickEventListener и OnLongClickEventListener.

Эти два интерфейса отвечают за прослушивание событий, которые могут произойти в компонентах пользовательского интерфейса, производных от виджетов, таких как Button или Checkbox. Поэтому, если мы сравним этот пример с более ранним примером Callback с использованием интерфейса Java, эти два интерфейса будут работать как интерфейс Callback. Таким образом, код более высокого уровня (Здесь Activity) реализует эти два интерфейса. И всякий раз, когда событие происходит с виджетом, будет вызываться код более высокого уровня (или метод этих интерфейсов, реализованный в коде более высокого уровня, который здесь называется Activity).

Теперь позвольте мне обсудить основное различие между шаблонами Callback и Eventlistener. Как мы уже упоминали, используя Callback, Callee может уведомить только одного абонента. Но в случае шаблона EventListener любая другая часть или класс приложения может регистрироваться для событий, которые могут происходить на кнопке или флажке. Примером такого класса является класс Other Class. Если вы увидите код Other Class, вы обнаружите, что он зарегистрировался в качестве прослушивателя ClickEvent, что может происходить в кнопке, определенной в Activity. Интересно, что помимо Activity (Вызывающий объект), этот Other Class также будет уведомляться всякий раз, когда происходит событие нажатия кнопки.

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

Альтернативный метод - запустить этот код параллельно и продолжить свою работу. Но что, если ваш исходный код должен делать разные вещи в зависимости от ответа вызываемого кода? Ну, в этом случае вы можете передать имя / местоположение кода, который вы хотите, чтобы он вызвал, когда это будет сделано. Это "обратный звонок".

Нормальный код: Запрос информации-> Информация о процессе-> Работа с результатами обработки-> Продолжать делать другие вещи.

С обратными вызовами: Запрос информации-> Информация о процессе-> Продолжать делать другие вещи. А на более позднем этапе -> дело с результатами обработки.

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

  • Сначала покажите своим студентам, как использовать и манипулировать переменными, используя указатели вместе с обычными идентификаторами переменных.
  • Затем научите их, что есть вещи, которые можно сделать только с помощью указателей (например, передача переменной по ссылке).
  • Затем скажите им, как исполняемый код или функции похожи на некоторые другие данные (или переменные) в памяти. Итак, функции также имеют адреса или указатели.
  • Затем покажите им, как функции можно вызывать с помощью указателей на функции, и скажите, что они называются обратными вызовами.
  • Теперь возникает вопрос, зачем все эти хлопоты по вызову некоторых функций? В чем выгода? Как и указатели данных, указатель на функцию или обратные вызовы имеет некоторые преимущества по сравнению с использованием обычных идентификаторов.
  • Во-первых, идентификаторы функций или имена функций не могут использоваться в качестве обычных данных. Я имею в виду, вы не можете создать структуру данных с функциями (например, массив или связанный список функций). Но с помощью обратных вызовов вы можете создать массив, связанный список или использовать их с другими данными, как в словаре пар ключ-значение или деревьев, или для любых других целей. Это мощное преимущество. И другие преимущества на самом деле ребенок этого.
  • Наиболее распространенное использование обратных вызовов наблюдается в программировании драйвера событий. Где одна или несколько функций выполняются на основе некоторого входящего сигнала. С помощью обратных вызовов можно поддерживать словарь для отображения сигналов с обратными вызовами. Тогда разрешение входного сигнала и выполнение соответствующего кода станут намного проще.
  • Второе использование обратных вызовов, приходящих мне на ум, - это функции высшего порядка. Функции, которые принимают другие функции в качестве входных аргументов. И для отправки функций в качестве аргументов нам нужны обратные вызовы. Примером может служить функция, которая принимает массив и обратный вызов. Затем он выполняет обратный вызов для каждого элемента массива и возвращает результаты в другом массиве. Если мы передадим функцию двойного обратного вызова, мы получим массив с двойным значением. Если мы передадим квадратный обратный вызов, мы получим квадраты. Для квадратных корней просто отправьте соответствующий обратный вызов. Это не может быть сделано с обычными функциями.

Там может быть еще много вещей. Вовлеките студентов, и они обнаружат. Надеюсь это поможет.

Я думаю, что это довольно легко объяснить.

Сначала обратный вызов - это просто обычные функции.
Кроме того, мы вызываем эту функцию (назовем ее A) из другой функции (назовем ее B).

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

В то время, когда я пишу функцию B I, я не знаю, какую функцию обратного вызова следует вызвать. В то время, когда я вызываю функцию B I, я также говорю этой функции вызывать функцию A. Это все.

Метафорическое объяснение:

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

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

(а) Я могу подождать в почтовом отделении, пока оно не будет доставлено.

(б) Я получу электронное письмо, когда оно будет доставлено.

Вариант (б) аналогичен обратному вызову.

На простом английском языке обратный вызов - это обещание. Джо, Джейн, Дэвид и Саманта вместе едут на машине. Джо сегодня за рулем. У Джейн, Дэвида и Саманты есть несколько вариантов:

  1. Проверяйте окно каждые 5 минут, чтобы увидеть, вышел ли Джо
  2. Продолжай делать свое дело, пока Джо не позвонит в дверь.

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

Вариант 2: это пример обратного вызова. Джейн говорит Джо позвонить в дверь, когда он на улице. Она дает ему "функцию", чтобы позвонить в дверь. Джо не нужно знать, как работает дверной звонок или где он находится, ему просто нужно вызвать эту функцию, т.е. позвонить в дверной звонок, когда он там.

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

С точки зрения приложений JavaScript и обратных вызовов. Нам также нужно понимать "замыкания" и контекст приложения. То, к чему относится "это", может легко запутать разработчиков JavaScript. В этом примере в методе / обратном вызове каждого человека "ring_the_door_bell()" могут быть некоторые другие методы, которые каждый человек должен сделать на основе своей утренней рутины ex. "выключить телевизор()". Мы бы хотели, чтобы "this" ссылалось на объект "Jane" или "David", чтобы каждый мог настроить все, что им нужно сделать, прежде чем Джо поднимет их. Это где настройка обратного вызова с Джо требует пародии метода, так что "это" относится к нужному объекту.

Надеюсь, это поможет!

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

Примером "реального мира" является местный магазин видеоигр. Вы ждете Half-Life 3. Вместо того, чтобы каждый день ходить в магазин, чтобы посмотреть, нет ли в нем, вы регистрируете свою электронную почту в списке, чтобы получать уведомления о появлении игры. Электронная почта становится вашим "обратным вызовом", а условие, которое необходимо выполнить, - это доступность игры.

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

Обратный вызов - это способ асинхронной обработки событий. Вы никогда не можете знать, когда будет выполнен обратный вызов, или если он будет выполнен вообще. Преимущество состоит в том, что он освобождает вашу программу и циклы ЦП для выполнения других задач в ожидании ответа.

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

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

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

Например, вы пишете таймер обратного вызова. Это позволяет вам указать продолжительность и функцию для вызова, и функция будет соответственно вызываться. "Запускать myfunction() каждые 10 секунд 5 раз"

Либо вы можете создать каталог функций, передав список имен функций и попросив библиотеку сделать обратный вызов соответственно. "Обратный вызов success() в случае успеха, обратный вызов fail() в случае неудачи".

Давайте посмотрим на простой пример указателя на функцию

void cbfunc()
{
     printf("called");
}

 int main ()
 {
                   /* function pointer */ 
      void (*callback)(void); 
                   /* point to your callback function */ 
      callback=(void *)cbfunc; 
                   /* perform callback */
      callback();
      return 0; 
}

Как передать аргумент в функцию обратного вызова?

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

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;   
}

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

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

Вот некоторые функции:

      def greeting(name):
    print("Hello " + name + "!")

def departing(name):
    print("Goodbye " + name + "!")

Вот функция (использующаякак параметр обратного вызова):

      def promptForName(ourCallback):
    myName = input("Enter Name:")
    ourCallback(myName)

Теперь давайте воспользуемся обратными вызовами!

      promptForName(greeting) 
# Enter Name: 
# >Ed
# Hello Ed!

promptForName(departing) 
# Enter Name: 
# >Ed
# Goodbye Ed!

promptForName(greeting) 
# Enter Name: 
# >Guy
# Hello Guy!

Я смог довольно быстро расширить свой код.


Адресация (неправильных и вводящих в заблуждение) ответов:

Обратные вызовы не означают асинхронность!

JS получил обещания примерно в 2015 году, а async/await — в ~2017. До этого использовались обратные вызовы.

Вот почему некоторые ответы здесь не имеют смысла, они объединяют два!

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

Обратные вызовы не означают управление событиями!

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

Обратные вызовы не означают закрытия!

Несмотря на то, что мой пример часто используется как аккуратный способ обеспечить закрытие, это не так.

Обратные вызовы не являются полным определением функций первого класса!

Это одна из многих функций, которые создают определение функций первого класса.

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

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

Обычно он вызывается, когда какая-то операция завершена. Так как вы создаете обратный вызов перед тем, как передать его другой функции, вы можете инициализировать его контекстной информацией с сайта вызова. Вот почему он называется call*back* - первая функция вызывает обратно в контекст, откуда она была вызвана.

[отредактировано], когда у нас есть две функции, скажем, functionA и functionB, если functionA зависит от functionB.

Затем мы вызываем функцию B как функцию обратного вызова. Это широко используется в среде Spring.

пример функции обратного вызова википедии

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

Меньше жесткого кода = проще в обслуживании и изменении = меньше времени = больше ценности для бизнеса = удивительность.

Например, в javascript, используя Underscore.js, вы можете найти все четные элементы в массиве, например так:

var evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
=> [2, 4, 6]

Пример любезно предоставлен Underscore.js: http://documentcloud.github.com/underscore/

Другие вопросы по тегам