Введение:

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

Понимание ада обратного вызова:

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

Предположим, мы создаем платформу электронной коммерции и хотим проверить все детали заказа пользователя. У нас есть несколько функций

getUser() чтобы получить информацию о пользователе

getOrders() для получения заказов пользователя

getProducts() для получения продуктов пользователя

function getUser(userId, callback) {
  setTimeout(() => {
    const user = { id: userId, name: 'John Doe' };
    callback(user);
  }, 1000);
}

function getOrders(userId, callback) {
  setTimeout(() => {
    const orders = ['Order 1', 'Order 2', 'Order 3'];
    callback(orders);
  }, 1000);
}

function getProducts(orders, callback) {
  setTimeout(() => {
    const products = ['Product 1', 'Product 2', 'Product 3'];
    callback(products);
  }, 1000);
}

Если мы хотим проверить детали заказа пользователя, мы должны использовать их как

getUser(123, (user) => {
  getOrders(user.id, (orders) => {
    getProducts(orders, (products) => {
          console.log(products);
    });
  });
});

Мы передаем getOrders() как обратный вызов getuser(), потому что getOrders() нужно вызывать после успешного завершения getUser(), аналогично getProducts() нужно вызывать после успешного завершения getOrders().

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

Проблемы и проблемы с Callback Hell:

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

Читаемость кода:

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

Обработка ошибок:

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

Сопровождаемость кода:

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

Методы преодоления Callback Hell:

Чтобы преодолеть ад обратных вызовов и улучшить читаемость и удобство сопровождения кода, можно использовать несколько методов. Вот несколько широко используемых подходов:

1. Обещания:

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

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

Мы можем изменить наши реализации функций как

function getUser(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const user = { id: userId, name: 'John Doe' };
      resolve(user);
    }, 1000);
  });
}

function getOrders(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const orders = ['Order 1', 'Order 2', 'Order 3'];
      resolve(orders);
    }, 1000);
  });
}

function getProducts(orders) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const products = ['Product 1', 'Product 2', 'Product 3'];
      resolve(products);
    }, 1000);
  });
}

Здесь Эта реализация использует промисы для обработки каждой асинхронной операции. Функции getUser, getOrders, getProducts возвращают обещания, которые разрешаются с соответствующими данными.

Теперь мы можем использовать цепочку промисов, как показано ниже.

getUser(123)
  .then((user) => getOrders(user.id))
  .then((orders) => getProducts(orders))
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.error('Error:', error);
  });

Теперь код стал чистым, читабельным и ремонтопригодным.

2. Асинхронно/ожидание:

Синтаксис async/await, представленный в современном JavaScript, упрощает обработку асинхронного кода. Это позволяет писать асинхронные операции синхронно, делая код более читабельным и поддерживая логический поток. Async/await основан на промисах и предоставляет интуитивно понятный способ обработки асинхронных операций.

Мы можем использовать async/await, чтобы сделать его более читабельным и удобным в сопровождении.

async function getOrderDetailsOfUser() {
  try {
    const user = await getUser(userId);
    const orders = await getOrders(user.id);
    const products = await getProducts(orders);
    console.log('Products saved successfully:', result);
  } catch (error) {
    console.error('Error:', error);
  }
}


getOrderDetailsOfUser();

Плюсы и минусы разных подходов:

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

Обещания:

Плюсы:

  • Улучшенная читаемость кода благодаря плоской структуре.
  • Встроенная обработка ошибок с помощью блоков catch.
  • Поддерживает цепочку операций, улучшая организацию кода.

Минусы:

  • Более крутая кривая обучения для разработчиков, плохо знакомых с Promises.
  • Могут потребоваться дополнительные усилия для преобразования существующего кода на основе обратного вызова.

Асинхронно/ожидание:

Плюсы:

  • Обеспечивает синхронное кодирование для асинхронных операций.
  • Улучшенная читаемость и удобство сопровождения благодаря линейной структуре кода.
  • Обработка ошибок через блоки try-catch.

Минусы:

  • Требуется современная среда JavaScript или транспиляция.
  • Может быть несовместим со старыми кодовыми базами или браузерами без надлежащей настройки.

Преодоление ада обратного вызова: лучшие практики:

Чтобы успешно преодолеть ад обратного вызова, следуйте этим рекомендациям:

  1. Определите проблемные участки кода с высокой вложенностью и определите области, в которых можно применить модульность.
  2. Рефакторинг кода путем разбиения сложных операций на более мелкие функции и использования Promises или async/await для обработки асинхронных задач.
  3. Используйте механизмы обработки ошибок, предоставляемые Promises или async/await, чтобы повысить надежность кода.

Заключение:

Ад обратного вызова — распространенная проблема в асинхронном программировании на JavaScript, но с помощью правильных методов и инструментов ее можно смягчить. Используя такие стратегии, как модульность, Promises, async/await и использование модульных библиотек, разработчики могут укротить ад обратных вызовов, в результате чего кодовые базы становятся более читабельными, удобными в сопровождении и надежными. Понимание и устранение ада обратных вызовов имеет решающее значение для создания высококачественных приложений JavaScript, которые являются масштабируемыми, устойчивыми к ошибкам и с которыми проще работать.

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