Фронтенд разработка

Обещания JavaScript: глубокое погружение в обработку ошибок и рекомендации

Изучение промисов в javascript с помощью этого практического руководства по обработке ошибок и рекомендациям по эффективному асинхронному программированию.

Обещание в кофейной чашке: раскрывая основы

Что такое обещания?

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

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

Почему обещания важны?

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

Но, как и многие вещи в программировании (и в жизни), промисы могут показаться сложными, пока вы к ним не привыкнете, потому что у них есть свои особенности, особенно когда дело касается обработки ошибок.

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

Готовы окунуться в мир Promises? Давайте погрузимся!

Расшифровка обещаний: анатомия и жизненный цикл

Анатомия обещания

Обещание JavaScript похоже на специальный объект, который связывает «производящий код», код, который что-то делает и требует времени, с «потребляющим кодом», кодом, который должен ждать результата. Прелесть промиса в том, что он может находиться в одном из трех состояний в любое время:

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

Жизненный цикл обещания

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

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

Это суть жизненного цикла Promise.

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

Навигация по ошибкам: обработка отклонений в промисах

Основы обработки ошибок

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

Когда промис отклоняется, это происходит из-за ошибки, возникающей где-то в операции промиса. Для обработки этих ошибок промисы в JavaScript используют специальные методы, в том числе .catch() и .finally().

let promise = new Promise((resolve, reject) => {
    throw new Error("Promise has been rejected!"); // An error occurred
});

// This will log: Error: Promise has been rejected!
promise.catch(error => console.log(error));

В приведенном выше примере мы создаем промис, который немедленно выдает ошибку. Затем мы обрабатываем эту ошибку, используя .catch().

Отклонение обещаний и распространение исключений

Метод .catch() во многом похож на блок try/catch. Когда Promise отклоняется, метод .catch() перехватывает ошибку и выполняет функцию для ее обработки.

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

let promise = new Promise((resolve, reject) => {
    reject("Promise has been rejected!"); // Promise gets rejected
});

promise
    .then(() => console.log("This won't run because the Promise was rejected."))
    .catch(error => console.log(error)); // This will log: Promise has been rejected!

В этом примере мы отклоняем обещание, поэтому .then() пропускается. .catch() улавливает причину отклонения и регистрирует ее.

Лучшие практики обработки ошибок

Важно помнить одну важную вещь: всегда заканчивайте свои цепочки промисов блоком .catch(). Даже если вы уверены, что ошибки не произойдет, всегда лучше перестраховаться, чем потом сожалеть.

Еще одна лучшая практика — использовать блоки .finally() после .catch(). Этот блок будет работать независимо от того, было ли обещание выполнено или отклонено, что идеально подходит для любых действий по очистке.

let promise = new Promise((resolve, reject) => {
    throw new Error("Promise has been rejected!"); // An error occurred
});

promise
    .then(() => console.log("This won't run because the Promise was rejected."))
    .catch(error => console.log(error)) // This will log: Error: Promise has been rejected!
    .finally(() => console.log("Clean-up activities go here."));

Здесь мы обрабатываем ошибку, а затем запускаем блок .finally() для любых действий по очистке. Этот блок .finally() выполняется независимо от того, было ли обещание выполнено или отклонено.

В следующей части мы рассмотрим некоторые дополнительные темы и посмотрим, как они вписываются в общую картину.

Изучение новых горизонтов: передовые методы обещаний

Искусство связывать обещания

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

firstFunction()  // this returns a promise
  .then(firstResult => secondFunction(firstResult))  // this also returns a promise
  .then(secondResult => console.log(secondResult))  // logs the result of the second function
  .catch(error => console.log(error));  // catches any error that occurred along the way

Promise.all(), Promise.race(), Promise.any() и Promise.allSettled()

JavaScript предоставляет нам несколько полезных методов для обработки нескольких промисов:

  • Promise.all(): принимает массив промисов и возвращает новый промис, который разрешается только тогда, когда все промисы в массиве разрешены.
Promise.all([promise1, promise2, promise3])
  .then(values => console.log(values))
  .catch(error => console.log(error));
  • Promise.race(): Учитывая массив промисов, он разрешает или отклоняет, как только одно из промисов в массиве разрешается или отклоняется со значением или причиной из этого промиса.
Promise.race([promise1, promise2, promise3])
  .then(value => console.log(value))
  .catch(error => console.log(error));
  • Promise.any(): принимает массив промисов и возвращает новый промис, который разрешается, как только разрешается одно из промисов в массиве.
Promise.any([promise1, promise2, promise3])
  .then(value => console.log(value))
  .catch(error => console.log(error));
  • Promise.allSettled(): принимает массив обещаний и возвращает новое обещание, которое разрешается, когда все обещания в массиве либо разрешены, либо отклонены.
Promise.allSettled([promise1, promise2, promise3])
  .then(values => console.log(values))
  .catch(error => console.log(error));

В следующем разделе мы рассмотрим реальный пример того, как можно использовать промисы.

В поле: применение промисов в реальном мире

Практический пример: получение данных из API

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

Рассмотрим сценарий, в котором мы создаем приложение погоды. Нам нужно получить данные о погоде из API, обработать данные, а затем отобразить их пользователю.

Вот как мы можем сделать это с промисами:

fetch('https://api.weatherapi.com/v1/current.json?key=YOUR_KEY&q=Rome')
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();  // This returns a promise
  })
  .then(data => {
    // Process and display the data here
    console.log(data);
  })
  .catch(error => console.log('There was an error: ', error));

В этом коде fetch() возвращает обещание, которое разрешается в объект Response, представляющий ответ на запрос. Затем мы используем метод .json(), который также возвращает Promise, для преобразования ответа в JSON. Наконец, мы обрабатываем и отображаем данные.

Если в какой-то момент произойдет ошибка (например, если запрос завершится неудачно или если статус ответа будет не в порядке), обещание будет отклонено, и мы перехватим ошибку в блоке .catch().

Создание мультисервисного агрегатора данных

Давайте представим более сложный сценарий: вы создаете мультисервисный агрегатор данных. Это приложение должно извлекать данные из нескольких API, обрабатывать данные, а затем объединять их в единый унифицированный набор данных. С промисами управление этим уровнем сложности становится намного более управляемым.

Вот пример того, как вы можете подойти к этому, используя Promise.all():

let urls = [
  'https://api.service1.com/data',
  'https://api.service2.com/data',
  'https://api.service3.com/data'
];

let requests = urls.map(url => fetch(url));

Promise.all(requests)
  .then(responses => Promise.all(responses.map(r => r.json())))
  .then(data => {
    // data now contains the data from all three services
    let aggregatedData = aggregateData(data);
    console.log(aggregatedData);
  })
  .catch(error => console.log('There was an error: ', error));

В этом коде мы начинаем с сопоставления нашего массива URL-адресов и инициируем запрос на выборку для каждого из них. Это дает нам массив промисов. Затем мы используем Promise.all(), чтобы дождаться, пока все эти промисы не будут разрешены. Каждое обещание выборки разрешается в объект Response, из которого мы можем извлечь данные JSON, вызвав метод .json() (который также возвращает обещание).

Снова используя Promise.all(), мы ждем, пока все данные JSON не будут извлечены, а затем передаем эти данные функции aggregateData(), которая объединяет данные из всех трех сервисов в один набор данных.

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

В следующем разделе мы обсудим лучшие практики работы с промисами.

Освоение искусства: лучшие практики использования промисов

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

Обеспечение читаемости кода с помощью промисов

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

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

doSomethingAsync()
  .then(result => doSomethingElseAsync(result))
  .then(newResult => doYetAnotherThingAsync(newResult))
  .then(finalResult => {
    console.log(`Got the final result: ${finalResult}`);
  })
  .catch(failureReason => {
    console.log(`Failed to get the final result: ${failureReason}`);
  });

Распространение ошибок и обработка

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

Используйте .catch() в конце цепочки промисов для обработки любых ошибок, которые могут возникнуть:

doSomethingAsync()
  .then(result => doSomethingElseAsync(result))
  .catch(error => {
    console.error(`Something went wrong: ${error}`);
  });

Если вам нужно обрабатывать определенные ошибки после каждой асинхронной операции, вы можете поместить .catch() после каждого .then():

doSomethingAsync()
  .then(result => doSomethingElseAsync(result))
  .catch(error => {
    console.error(`Something went wrong after the first operation: ${error}`);
    throw error;  // This will propagate the error to the next catch block
  })
  .then(result => doYetAnotherThingAsync(result))
  .catch(error => {
    console.error(`Something went wrong after the second operation: ${error}`);
  });

Тестирование обещаний

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

it('should do something async', () => {
  return doSomethingAsync().then(result => {
    expect(result).toBe('expected value');
  });
});

Эти рекомендации должны помочь вам эффективно использовать промисы для управления асинхронным кодом JavaScript.

В следующем разделе мы подытожим все, что узнали о промисах в JavaScript.

Шаг назад: заключение

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

Если вы нашли это руководство полезным, вам также могут понравиться мои статьи Функции высшего порядка и Генераторы и итераторы в JavaScript. Оставайтесь с нами для более глубокого погружения в темы JavaScript!





Спасибо, что присоединились ко мне в этой статье через JavaScript Promises, и до следующего раза, удачного кодирования!

🔔 Дружеское напоминание: если вы решите приобрести эту книгу, вы поддержите мою работу без каких-либо дополнительных затрат с вашей стороны. Вы получите удовольствие от прочтения, а я смогу продолжать создавать контент, который вам нравится. Ваша поддержка моей страсти очень ценится!

И, если вам понравилось это чтение, не забудьте нажать кнопку «Подписаться», чтобы получить более полный контент, подобный этому! 😉👨‍💻 #Средний #Следуй за мной

Дополнительные материалы на PlainEnglish.io.

Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord .