Callback function
A callback function is a function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action.
The consumer of a callback-based API writes a function that is passed into the API. The provider of the API (called the caller) takes the function and calls back (or executes) the function at some point inside the caller’s body. The caller is responsible for passing the right parameters into the callback function. The caller may also expect a particular return value from the callback function, which is used to instruct further behavior of the caller.
There are two ways in which the callback may be called: synchronous and asynchronous. Synchronous callbacks are called immediately after the invocation of the outer function, with no intervening asynchronous tasks, while asynchronous callbacks are called at some point later, after an asynchronous operation has completed.
Understanding whether the callback is synchronously or asynchronously called is particularly important when analyzing side effects. Consider the following example:
let value = 1; doSomething(() => value = 2; >); console.log(value);
If doSomething calls the callback synchronously, then the last statement would log 2 because value = 2 is synchronously executed; otherwise, if the callback is asynchronous, the last statement would log 1 because value = 2 is only executed after the console.log statement.
Examples of synchronous callbacks include the callbacks passed to Array.prototype.map() , Array.prototype.forEach() , etc. Examples of asynchronous callbacks include the callbacks passed to setTimeout() and Promise.prototype.then() .
The Using promises guide has more information on the timing of asynchronous callbacks.
See also
- Callback on Wikipedia
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
This page was last modified on Jun 29, 2023 by MDN contributors.
Введение: колбэки
Для демонстрации использования колбэков, промисов и других абстрактных понятий мы будем использовать некоторые браузерные методы: в частности, загрузку скриптов и выполнение простых манипуляций с документом.
Если вы не знакомы с этими методами, и их использование в примерах вызывает у вас недоумение, возможно, вам стоит прочитать несколько глав из следующей части учебника.
Тем не менее, мы все равно попытаемся максимально доходчиво всё разъяснить. Ничего особо сложного в плане браузера не будет.
Многие действия в 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* одноразовые, и созданы лишь только, чтобы избавиться от «адской пирамиды вызовов». Никто не будет их переиспользовать где-либо ещё. Таким образом, мы, кроме всего прочего, засоряем пространство имён.
Нужно найти способ получше.
К счастью, такие способы существуют. Один из лучших — использовать промисы, о которых рассказано в следующей главе.
Что такое 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
Что такое callback в JavaScript?
Представьте, что вам пять лет. Вы едва что-то знаете о мире. И сейчас вы изучите одну из самых замысловатых концепций в JavaScript — callback.
Эй ты, функция.
Представьте, что JavaScript — это большой король, который дает задачи маленьким людям.
В мире JavaScript-а маленькие люди, которые выполняют задачи, называются функциями. Функции могут выполнять любую задачу, которую им прикажет JavaScript.
Момент, когда JavaScript говорит функции выполнить какую-то задачу, называется вызовом функции.
Но не все функции одинаковы. Некоторые из них заранее знают, что они должны делать. Как функция выше, которая говорит, что Javascript король. Бедная функция.
Другие функции более гибкие. Они также знают, что они должны делать, но не знают, с чем именно они должны это делать, пока им этого не скажет Javascript.
Пока JavaScript не назвал цифры, первая функция их не знает. Все, что она знает, так это то, что она должна делать — принимать какие-то данные, в нашем случае это числа, и называть их сумму. Но не знает, какие именно это будут данные.
В мире JavaScript данные, которые принимает функция, называются аргументами. А значит два числа, которые принимает функция выше, называются аргументами.
Если JavaScript потом скажет этой функции принимать вместо двух чисел, три числа и еще принимать строку текста, и что-то с этим всем сделать, то эти числа и текст также будут называться аргументами.
Теперь, когда познакомились с функциями и их аргументами, пора представить вам колбэка.
Кто, черт возьми, этот callback?
Как видите, JavaScript носит большую корону на голове. Это значит, что он король своего мира и может делать в нем все, что захочет. Например, он может сказать одной функции принять другую функцию а затем ее вызвать.
Как мы помним, все данные, которые принимает функция, называются аргументами. Значит, кроме принимаемых чисел, функцию, которую принимает первая функция, можно также назвать аргументом.
В мире JavaScript действует правило — если одна функция принимает другую функцию в качестве аргумента (1) и вызывает ее внутри себя (2), принятая функция называется функцией callback. Или более сокращенное название — callback.
В нашем примере, первая функция принимает имя второй функции (1) и вызывает ее внутри своей задачи, то есть себя (2). Из этого следуют, что функция номер два — это callback.
Функции по-прежнему называются функциями. Но если они принимаются в качестве аргументов в других функциях, и там вызываются, они называются callback.
Вот так устроен мир JavaScript.
