W3docs

Функции обратного вызова в JavaScript

Коллбэки в JavaScript — ключевая концепция для работы с асинхронными операциями. Это функции, передаваемые в качестве аргументов другим функциям.

Коллбэк (callback) — это просто функция, которую вы передаёте в другую функцию в качестве аргумента, чтобы принимающая функция могла вызвать её позже. Поскольку функции в JavaScript являются значениями первого класса — их можно хранить в переменных, передавать и возвращать — любая функция может принимать другую функцию в качестве параметра. Эта идея лежит в основе всего: от методов array вроде map до таймеров и сетевых запросов.

В этой главе рассматривается, что такое коллбэки, различие между синхронными и асинхронными коллбэками, соглашение error-first, проблема callback hell, а также то, как Promises и async/await решают её.

Первый коллбэк

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

javascript— editable

finishTask передаётся в completeTask и вызывается внутри неё. Обратите внимание: вы передаёте имя функции (finishTask) без скобок — если добавить (), функция вызовется немедленно, и вместо неё передастся её возвращаемое значение.

Синхронные и асинхронные коллбэки

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

Синхронные коллбэки

Синхронный коллбэк выполняется немедленно, по порядку, до того как внешняя функция возвращает управление. Классический пример — методы array:

javascript— editable

Здесь transform вызывается и завершается для каждого элемента до того, как map вернёт результат. Встроенные методы Array.prototype.map, filter, forEach и sort принимают синхронные коллбэки.

Асинхронные коллбэки

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

javascript— editable

Даже при задержке 0мс коллбэк выполняется после окружающего синхронного кода. Это определяющая черта асинхронного коллбэка: он не может вернуть значение обычным способом, поэтому единственный способ использовать результат — поместить код продолжения внутрь коллбэка. Чтобы точно понять, когда они выполняются, изучите JavaScript: Event Loop.

Соглашение error-first

Асинхронные коллбэки не могут вызвать throw в коде, инициировавшем операцию — к тому моменту, когда они выполняются, тот код уже давно вернул управление. Сообщество выработало соглашение: передавать ошибку первым аргументом, а результат — вторым. Коллбэк всегда проверяет ошибку первой.

javascript— editable

Когда всё проходит успешно, слот ошибки содержит null. Сигнатура (err, result) является стандартной в API Node.js (fs.readFile, dns.lookup и многих других).

Callback Hell: пирамида судьбы

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

javascript— editable

При двух шагах код ещё читаем. Добавьте третий и четвёртый — и повторите проверку if (err) на каждом уровне — и код станет трудно читать, обрабатывать ошибки в нём и вносить изменения. Это и есть callback hell, и именно по этой причине были введены Promises.

Лучшие практики использования коллбэков

Несмотря на то что коллбэки — мощный инструмент, их чрезмерное или неправильное использование может привести к «callback hell», когда код становится слишком глубоко вложенным и его сложно читать и поддерживать. Вот несколько лучших практик, которые помогут поддерживать код в чистоте:

  1. Разбивайте код на модули: разделяйте функции коллбэков на более мелкие, многократно используемые функции. Такой подход улучшает читаемость и облегчает сопровождение кода.
  2. Грамотно обрабатывайте ошибки: всегда обрабатывайте ошибки в коллбэках. Это предотвращает сбои и нежелательное поведение в production-окружениях.
  3. Избегайте глубокой вложенности: старайтесь по возможности уплощать структуры коллбэков. Инструменты вроде async/await или Promises помогают управлять асинхронными операциями чище.
  4. Учитывайте замыкания в циклах: при объявлении коллбэков внутри циклов используйте let или const для переменных цикла, чтобы избежать связанных с замыканиями ошибок, когда все коллбэки захватывают финальное значение переменной цикла.

Выход за рамки коллбэков: Promises и Async/Await

Хотя коллбэки являются фундаментальной частью JavaScript, современный JavaScript предлагает более абстрактные способы работы с асинхронным кодом: Promises и async/await.

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

Promise представляет значение, которое может быть доступно сейчас, в будущем или никогда. Вместо вложенности зависимые шаги выстраиваются в цепочку один за другим, что уплощает пирамиду и централизует обработку ошибок в одном .catch. Начните с JavaScript: Promises, затем посмотрите, как шаги соединяются в JavaScript: Promises Chaining.

Если у вас есть старая функция на основе коллбэков (например, divide или getUser из примеров выше), вы можете обернуть её так, чтобы она возвращала Promise — эта техника называется promisification. См. JavaScript: Promisification.

Async/Await: более чистый подход

Синтаксис async/await позволяет писать асинхронный код, который читается как синхронный. Он построен поверх Promises и более интуитивен, чем традиционные паттерны с коллбэками. Прочитайте JavaScript: Async/Await, чтобы узнать, как использовать async/await вместо коллбэков.

Заключение

Понимание и эффективное использование коллбэков критически важно для JavaScript-разработчиков. Следуя лучшим практикам и используя современные возможности вроде Promises и async/await, вы можете писать более чистый и поддерживаемый код. Освойте эти концепции, чтобы улучшить свои навыки программирования на JavaScript и создавать более эффективные приложения.

Практика

Практика
Что такое функция обратного вызова в JavaScript и когда она выполняется?
Что такое функция обратного вызова в JavaScript и когда она выполняется?
Was this page helpful?