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

Почему мы используем обратные вызовы?

Ядро среды выполнения JavaScript является однопоточным и может выполнять только одно действие за раз, то есть оно синхронизировано. Однако в некоторых случаях код запускается (или должен запускаться) после того, как что-то еще происходит, а также не последовательно. Это называется асинхронным программированием.

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

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

Как мы используем обратные вызовы?

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

Когда мы используем обратные вызовы?

События взаимодействия с пользователем
JavaScript дает вашей программе возможность реагировать на события браузера, особенно на события пользователя.

Фрагмент выше просто присваивает элемент profile const и добавляет прослушиватель событий, когда пользователь щелкает этот элемент, вызывается sayMyName() и регистрирует мое имя.

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

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

Ждем ответа

Это не хитрый фрагмент, и то, что вы ожидаете, произойдет.
JavaScript будет логировать

one
two

в этом порядке.

Сложная часть возникает, когда первой функции нужно дождаться возврата какого-либо ответа или завершения процесса, например, — вызов API, чтение файла, получение данных из БД и т. д. Чтобы имитировать этот процесс, мы можем используйте функцию setTimeout.

setTimeout() принимает два аргумента. Во-первых, функция для выполнения, а во-вторых, время в миллисекундах, после которого код должен быть выполнен.

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

С учетом сказанного, на этот раз наш код будет печатать

two
one

в этом порядке.

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

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

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

Пирамида гибели

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

Давайте посмотрим на приведенный выше пример по-другому.

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

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

Вложение многих асинхронных функций в обратные вызовы известно как пирамида гибели или ад обратных вызовов.

Чтобы их избежать, вы используете обещания или функции async/await.