Введение: колбэки
Для демонстрации использования колбэков, промисов и других абстрактных понятий мы будем использовать некоторые браузерные методы: в частности, загрузку скриптов и выполнение простых манипуляций с документом.
Если вы не знакомы с этими методами, и их использование в примерах вызывает у вас недоумение, возможно, вам стоит прочитать несколько глав из следующей части учебника.
Тем не менее, мы все равно попытаемся максимально доходчиво всё разъяснить. Ничего особо сложного в плане браузера не будет.
Многие действия в JavaScript асинхронные.
Например, рассмотрим функцию loadScript(src) :
function loadScript(src)
Эта функция загружает на страницу новый скрипт. Когда в тело документа добавится конструкция , браузер загрузит скрипт и выполнит его.
Вот пример использования этой функции:
// загрузит и выполнит скрипт loadScript('/my/script.js');
Такие функции называют «асинхронными», потому что действие (загрузка скрипта) будет завершено не сейчас, а потом.
Если после вызова loadScript(…) есть какой-то код, то он не будет ждать, пока скрипт загрузится.
loadScript('/my/script.js'); // код, написанный после вызова функции loadScript, // не будет дожидаться полной загрузки скрипта // .
Мы хотели бы использовать новый скрипт, как только он будет загружен. Скажем, он объявляет новую функцию, которую мы хотим выполнить.
Но если мы просто вызовем эту функцию после loadScript(…) , у нас ничего не выйдет:
loadScript('/my/script.js'); // в скрипте есть "function newFunction() " newFunction(); // такой функции не существует!
Действительно, ведь у браузера не было времени загрузить скрипт. Сейчас функция loadScript никак не позволяет отследить момент загрузки. Скрипт загружается, а потом выполняется. Но нам нужно точно знать, когда это произойдёт, чтобы использовать функции и переменные из этого скрипта.
Давайте передадим функцию callback вторым аргументом в loadScript , чтобы вызвать её, когда скрипт загрузится:
function loadScript(src, callback) < let script = document.createElement('script'); script.src = src; script.onload = () =>callback(script); document.head.append(script); >
Событие onload описано в статье Загрузка ресурсов: onload и onerror, оно в основном выполняет функцию после загрузки и выполнения скрипта.
Теперь, если мы хотим вызвать функцию из скрипта, нужно делать это в колбэке:
loadScript('/my/script.js', function() < // эта функция вызовется после того, как загрузится скрипт newFunction(); // теперь всё работает . >);
Смысл такой: вторым аргументом передаётся функция (обычно анонимная), которая выполняется по завершении действия.
Возьмём для примера реальный скрипт с библиотекой функций:
function loadScript(src, callback) < let script = document.createElement('script'); script.src = src; script.onload = () =>callback(script); document.head.append(script); > loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', script => < alert(`Здорово, скрипт $загрузился`); alert( _ ); // функция, объявленная в загруженном скрипте >);
Такое написание называют асинхронным программированием с использованием колбэков. В функции, которые выполняют какие-либо асинхронные операции, передаётся аргумент callback — функция, которая будет вызвана по завершению асинхронного действия.
Мы поступили похожим образом в loadScript , но это, конечно, распространённый подход.
Колбэк в колбэке
Как нам загрузить два скрипта один за другим: сначала первый, а за ним второй?
Первое, что приходит в голову, вызвать loadScript ещё раз уже внутри колбэка, вот так:
loadScript('/my/script.js', function(script) < alert(`Здорово, скрипт $загрузился, загрузим ещё один`); loadScript('/my/script2.js', function(script) < alert(`Здорово, второй скрипт загрузился`); >); >);
Когда внешняя функция loadScript выполнится, вызовется та, что внутри колбэка.
А что если нам нужно загрузить ещё один скрипт.
loadScript('/my/script.js', function(script) < loadScript('/my/script2.js', function(script) < loadScript('/my/script3.js', function(script) < // . и так далее, пока все скрипты не будут загружены >); >) >);
Каждое новое действие мы вынуждены вызывать внутри колбэка. Этот вариант подойдёт, когда у нас одно-два действия, но для большего количества уже не удобно. Альтернативные подходы мы скоро разберём.
Перехват ошибок
В примерах выше мы не думали об ошибках. А что если загрузить скрипт не удалось? Колбэк должен уметь реагировать на возможные проблемы.
Ниже улучшенная версия loadScript , которая умеет отслеживать ошибки загрузки:
function loadScript(src, callback) < let script = document.createElement('script'); script.src = src; script.onload = () =>callback(null, script); script.onerror = () => callback(new Error(`Не удалось загрузить скрипт $`)); document.head.append(script); >
Мы вызываем callback(null, script) в случае успешной загрузки и callback(error) , если загрузить скрипт не удалось.
loadScript('/my/script.js', function(error, script) < if (error) < // обрабатываем ошибку >else < // скрипт успешно загружен >>);
Опять же, подход, который мы использовали в loadScript , также распространён и называется «колбэк с первым аргументом-ошибкой» («error-first callback»).
- Первый аргумент функции callback зарезервирован для ошибки. В этом случае вызов выглядит вот так: callback(err) .
- Второй и последующие аргументы — для результатов выполнения. В этом случае вызов выглядит вот так: callback(null, result1, result2…) .
Одна и та же функция callback используется и для информирования об ошибке, и для передачи результатов.
Адская пирамида вызовов
На первый взгляд это рабочий способ написания асинхронного кода. Так и есть. Для одного или двух вложенных вызовов всё выглядит нормально.
Но для нескольких асинхронных действий, которые нужно выполнить друг за другом, код выглядит вот так:
loadScript('1.js', function(error, script) < if (error) < handleError(error); >else < // . loadScript('2.js', function(error, script) < if (error) < handleError(error); >else < // . loadScript('3.js', function(error, script) < if (error) < handleError(error); >else < // . и так далее, пока все скрипты не будут загружены (*) >>); > >) > >);
- Мы загружаем 1.js . Продолжаем, если нет ошибок.
- Мы загружаем 2.js . Продолжаем, если нет ошибок.
- Мы загружаем 3.js . Продолжаем, если нет ошибок. И так далее (*) .
Чем больше вложенных вызовов, тем наш код будет иметь всё большую вложенность, которую сложно поддерживать, особенно если вместо . у нас код, содержащий другие цепочки вызовов, условия и т.д.
Иногда это называют «адом колбэков» или «адской пирамидой колбэков».
Пирамида вложенных вызовов растёт вправо с каждым асинхронным действием. В итоге вы сами будете путаться, где что есть.
Такой подход к написанию кода не приветствуется.
Мы можем попытаться решить эту проблему, изолируя каждое действие в отдельную функцию, вот так:
loadScript('1.js', step1); function step1(error, script) < if (error) < handleError(error); >else < // . loadScript('2.js', step2); >> function step2(error, script) < if (error) < handleError(error); >else < // . loadScript('3.js', step3); >> function step3(error, script) < if (error) < handleError(error); >else < // . и так далее, пока все скрипты не будут загружены (*) >>;
Заметили? Этот код делает всё то же самое, но вложенность отсутствует, потому что все действия вынесены в отдельные функции.
Код абсолютно рабочий, но кажется разорванным на куски. Его трудно читать, вы наверняка заметили это. Приходится прыгать глазами между кусками кода, когда пытаешься его прочесть. Это неудобно, особенно, если читатель не знаком с кодом и не знает, что за чем следует.
Кроме того, все функции step* одноразовые, и созданы лишь только, чтобы избавиться от «адской пирамиды вызовов». Никто не будет их переиспользовать где-либо ещё. Таким образом, мы, кроме всего прочего, засоряем пространство имён.
Нужно найти способ получше.
К счастью, такие способы существуют. Один из лучших — использовать промисы, о которых рассказано в следующей главе.
Колбэк-функция
Колбэк-функция (или обратный вызов) — это функция, переданная в другую функцию в качестве аргумента, которая затем вызывается по завершению какого-либо действия.
Вот краткий пример:
function greeting(name) alert("Hello " + name); > function processUserInput(callback) var name = prompt("Please enter your name."); callback(name); > processUserInput(greeting);
Выше приведён пример синхронного колбэка, поскольку функция processUserInput выполняется синхронно.
Колбэки часто используются для продолжения выполнения кода после завершения асинхронной операции — они называются асинхронными колбэками.
async function pageLoader(callback) const data = await fetch("/ru/docs/Glossary/Callback_function"); callback(data); > function onPageLoadingFinished(pageData) console.log("Page was sucessfully loaded!"); console.log("Response:"); console.log(pageData); > pageLoader(onPageLoadingFinished);
Вот ещё один пример асинхронного обратного вызова: maps-example.html (живой пример). Он использует Google Maps API и Geolocation API для отображения карты текущего местоположения вашего устройства.
// maps-example.html // Вызов асинхронной функции getCurrentPosition // с передачей callback функции принимающей координаты // в качестве параметра navigator.geolocation.getCurrentPosition(function (position) var latlng = new google.maps.LatLng( position.coords.latitude, position.coords.longitude, ); var myOptions = zoom: 8, center: latlng, mapTypeId: google.maps.MapTypeId.TERRAIN, disableDefaultUI: true, >; var map = new google.maps.Map( document.getElementById("map_canvas"), myOptions, ); >);
Поскольку получение координат устройства из его GPS является асинхронным (мы точно не знаем, когда данные будут возвращены), метод Geolocation.getCurrentPosition() принимает анонимную колбэк-функцию в качестве параметра, которая получает найденные данные координат. Эта функция выполняется только по возвращению данных координат.
Больше информации
Основное
- Колбэки и события на компонентах
- Ад обратных вызовов
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
Урок 13. Основы JAVA. Методы обратного вызова (callback)
Механизм обратного вызова(callbacks) широко распространен в программировании. При обратном вызове программист задает действия, которые должны выполняться всякий раз, когда происходит некоторое событие. И неважно, будете разрабатывать только java-программы или android-приложения – колбеки будут встречаться вам повсюду.
Для создания методов обратного вызова (callback) в java используются уже знакомые нам из прошлого урока интерфейсы.
Создадим новый класс SomeClass с интерфейсом Callback:
import javax.swing.*; public class SomeClass < // создаем колбек и его метод interface Callback< void callingBack(); >Callback callback; public void registerCallBack(Callback callback) < this.callback = callback; >void doSomething() < JOptionPane.showMessageDialog(null, "Выполняется работа"); // вызываем метод обратного вызова callback.callingBack(); >>
У интерфейса мы определили один метод callingBack(). Далее в классе мы создали объект интерфейса и инициализировали его в методе registerCallBack. В классе также был создан метод doSomething(), в котором может содержаться какой-то сложный код. Мы здесь создаем диалоговое окно, используя компонент JOptionPane библиотеки Swing. Кто не в курсе, Swing — библиотека для создания графического интерфейса программ на языке Java.
В конце работы метода doSomething() вызывается метод интерфейса callingBack() . В данном случае мы сами создали метод и знаем его код. Но во многих случаях, вы будете использовать готовый метод какого-то класса и можете даже не знать, что именно содержится в этом методе. Вам надо только знать, что такой метод существует и выполняет конкретную задачу.
Теперь нам нужен класс, реализующий интерфейс-callback. Создаем класс MyClass и подключаем интерфейс-колбек через ключевое слово implements. Среда разработки заставит нас реализовать шаблон метода интерфейса.
public class MyClass implements SomeClass.Callback < @Override public void callingBack() < System.out.println("Вызов метода обратного вызова"); >>
Теперь мы можем использовать метод обратного вызова callingBack () для решения своих задач. Будем здесь выводить текст в консоль. Создадим класс Main, в котором будет реализован метод main – точка входа в программу.
public class Main < public static void main(String[] args) < SomeClass someClass = new SomeClass(); MyClass myClass = new MyClass(); //инициализируем колбек, передавая методу registerCallBack экземпляр MyClass, реализующий интерфейс колбек someClass.registerCallBack(myClass); someClass.doSomething(); >>
Здесь создаем экземпляры классов, инициализируем колбек, передавая методу registerCallBack экземпляр MyClass, реализующий интерфейс-callback. И вызываем метод doSomething();
При старте программы будет выполняться код метода doSomething(); из класса SomeClass. На экране появилось диалоговое окно.
Когда мы нажмем кнопку, или закроем диалоговое окно, сработает метод обратного вызова callingBack(), который выведет сообщение в консоль.
Через интерфейс-callback также можно передавать какие-то данные. Давайте немного усложним пример.
package callback; import javax.swing.*; public class SomeClass < String replyTo; interface Callback< void callingBack(String s); >private Callback callback; public void registerCallBack(Callback callback) < this.callback = callback; >void doSomething() < int reply = JOptionPane.showConfirmDialog(null, "Вы программист?", "Опрос", JOptionPane.YES_NO_OPTION); if (reply == JOptionPane.NO_OPTION)< replyTo = "Нет"; >if (reply == JOptionPane.YES_OPTION) < replyTo = "Да"; >callback.callingBack(replyTo); > >
Объявим строковую переменную replyTo. Добавим аргумент метода callingBack – строковую переменную.
Изменим диалоговое окно – присвоим его новой целочисленной переменной reply, и изменим на showConfirmDialog, который имеет заголовок окна и больше одной кнопки ответа. В зависимости от нажатой кнопки переменной reply будет присвоено значение. Сравниваем ее значение с константой и присваиваем значение “Да” или “Нет” переменной replyTo.
Теперь внесем изменения в MyClass.
package callback; public class MyClass implements SomeClass.Callback < @Override public void callingBack(String s) < if (s != null) < System.out.println("Ваш ответ: " + s); >else < System.out.println("Вы не ответили на вопрос!"); >> >
Добавим аргумент в метод. И добавим условие проверки, что строка не пустая – это возможно, если вместо ответа просто закрыть диалоговое окно.
Класс Main без изменений.
package callback; public class Main < public static void main(String[] args) < SomeClass someClass = new SomeClass(); MyClass myClass = new MyClass(); someClass.registerCallBack(myClass); someClass.doSomething(); >>
Запускаем программу. Теперь текст сообщения в консоли меняется в зависимости от того, выберем мы да, нет или просто закроем окно.

Я надеюсь, вы поняли принцип методов обратного вызова. Ниже есть ссылка на дополнительную информацию по колбекам, а также более сложные и познавательные примеры. Вопросы можно задавать в комментариях.
Введение в колбэк-функции в JavaScript
При изучении программирования мы привыкаем мыслить последовательно: строки кода выполняются по порядку. Для многих языков это утверждение верно на 100%, но всё начинает меняться, когда речь заходит про асинхронное программирование.
Это статья о том, как работают и зачем нужны колбэк-функции в JavaScript. Это одна из важнейших тем программирования на JavaScript, и ни одна более-менее серьёзная программа не обойдется без применения колбэк-функций.
Что такое колбэк-функция
Колбэк-функция или функция обратного вызова — функция, предназначенная для отложенного выполнения. Проще говоря, она должна быть выполнена после завершения работы другой функции. Чтобы стало понятнее, разберём пример с заказом пиццы.
Колбэк (callback) переводится как «Перезвоните». Действительно, принцип работы колбэков схож с заказом обратного телефонного звонка. Представьте, что вы звоните оператору для заказа пиццы, но срабатывает автоответчик, где приятный голос просит оставаться на линии, пока не освободится оператор, или предлагает заказать обратный звонок. Когда оператор освободится — он перезвонит и примет заказ.
Это прекрасная аналогия для понимания принципов работы колбэков и асинхронности. Вместо ожидания ответа оператора, мы можем заказать обратный звонок и заниматься другими делами. Как только произойдёт колбэк (нам перезвонили), мы сможем выполнить задуманное — заказать пиццу.
Как писать код для колбэков
Посмотрим на колбэки с практической стороны.
Выше мы сказали, что колбэки неразрывно связаны с асинхронностью и позволяют «запланировать» действие, которое будет совершено после выполнения какого-то другого, возможно длительного действия. Пример с заказом пиццы это прекрасно илюстрирует. Давайте посмотрим, как это может выглядеть в коде, но для начала взглянем на синхронный код:
// Приготовление пиццы. Длительный процесс const newPizza = makePizza('pepperoni'); // Читаем книгу пока готовят пиццу readBook(); // Съедаем пиццу eatPizza(newPizza);
Что в этом коде больше всего бросается в глаза? Правильно — последовательность. Здесь представлен синхронный код, который будет выполнятся последовательно:
- Мы ждём, пока для нас приготовят пиццу «Пепперони».
- Затем мы читаем книгу.
- Наконец-таки откладываем книгу в сторону и ужинаем пиццей.
Проблема видна невооруженным глазом — пока готовится пицца, мы вынуждены ждать и ничего не делать. Строка readBook() будет выполнена только после приготовления пиццы. Фактически мы начнём читать книгу после приготовления пиццы, а не во время готовки.
Само собой, в реальном мире вместо выпекания пиццы может быть любой долгий процесс, например, запрос на получение данных с сервера.
Такой запрос не выполняется мгновенно: браузеру понадобится время, чтобы найти IP-адрес сервера, установить соединение, передать запрос, дождаться ответа и т. д. Все эти действия занимают разное количество времени. Временные задержки будут постоянно отличаться и зависеть от скорости соединения с сетью, времени выполнения запроса на сервере и некоторых других факторов.
Синхронные запросы к серверу будут блокировать дальнейшее выполнение веб-приложения, и это уже очень плохо. Представьте, что каждый раз при отправке запроса к серверу интерфейс вашего приложения становится полностью недоступным.
Эту проблему решает асинхронность, и длительные операции лучше выполнять именно асинхронно. В этом варианте мы как бы откладываем длительную операцию «на потом» и вместо ожидания завершения выполняем другой код. В этой схеме прозрачно всё, кроме вопроса: «Как выполнить код после завершения асинхронной операции?». Ответ прост — функции обратного вызова.
В JavaScript функции являются объектами высшего порядка. Это означает, что функции можно передавать в другие функции в виде параметров или возвращать в виде результата выполнения.
const foo = function () < return 'Hello, world!'; >// Вызываем функцию и выводим результат в консоль console.log(foo()); // Hello, world // Выводим функцию в консоль без вызова console.log(foo); // ƒ ()
В первом случае мы вызываем функцию foo при помощи круглых скобок и выводим результат выполнения в консоль. Во втором примере мы не делаем вызов функции (обратите внимание на отсутствие круглых скобок), и в консоль выводится содержимое функции. Выходит, нам ничего не мешает передать функцию в виде параметра для других функций:
const runIt = function (fn) < return fn(); // Вызываем функцию, переданную в качестве параметра >console.log(runIt(foo)); // Hello, world
Мы передали функцию foo в виде параметра и вызывали её внутри функции runIt . Вызов функции мы сделали стандартным образом — применяя круглые скобки.
Что в итоге? Мы передали ссылку на функцию в виде параметра и вызвали её внутри другой функции. В этом и заключается идея колбэков: мы передаем в виде параметров функции, которые будут вызваны «когда-нибудь потом».
И снова пицца
Вернёмся к примеру с приготовлением пиццы. Попробуем поэкспериментировать с кодом и перевести его на асинхронные рельсы. Напомню, наша задача — попросить приготовить пиццу, и читать книгу, пока пицца не будет готова.
const makePizza = function (title, cb) < console.log(`Заказ на приготовление пиццы «$» получен. Начинаем готовить…`); setTimeout(cb, 3000); > const readBook = function () < console.log('Читаю книгу «Колдун и кристалл»…'); >const eatPizza = function () < console.log('Ура! Пицца готова, пора подкрепиться.'); >makePizza('Пеперонни', eatPizza); readBook();
Это рабочий код, попробуйте выполнить его в консоли и посмотреть на результат вывода. Он будет таким:
Заказ на приготовление пиццы «Пепперони» получен. Начинаем готовить… Читаю книгу «Колдун и кристалл»… // Здесь будет пауза Ура! Пицца готова, пора подкрепиться.
Функция makePizza выполняется мгновенно, и сразу за ней последовал вызов readBook . Пока мы читали книгу, приготовилась пицца, и произошёл вызов функции eatPizza из функции makePizza . Задержку выполнения мы сделали искусственно с помощью setTimeout .
Как видите, ничего сверхъестественного в колбэках нет. Это обычная функция, которая будет выполнена не сейчас, а когда-нибудь потом. «Когда-нибудь» — не преувеличение. Мы не можем сказать, в какой момент времени это случится, но можем сказать, после какой именно функции — после выполнения функции приготовлении пиццы.
«Доктайп» — журнал о фронтенде. Читайте, слушайте и учитесь с нами.
