Асинхронность и промисы

Статья большой получилась из-за очень большого числа примеров кода. Не нужно страшится её размеров. Дочитать до конца серьёзного труда не составит.

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

  1. Колбеки и их недостатки.
  2. Промисы, включая создание и работу с цепочками промисов.
  3. Использование конструкции async/await для упрощения работы с асинхронным кодом.

Колбеки и проблемы с ними

Колбек (callback) – это функция, которая передается другой функции в качестве аргумента и выполняется после того, как завершится некоторая операция. Колбеки позволяют выполнять асинхронный код последовательно, но при этом они имеют ряд недостатков.

Пример использования колбека:

Здесь функция console.log() является колбеком, который будет выполнен спустя одну секунду.

Проблемы с использованием колбеков

Callback Hell («ад колбеков»): Когда несколько асинхронных операций выполняются одна за другой, код становится трудно читаемым и сложно поддерживаемым. Вот пример callback hell:

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

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

Трудности с управлением потоками выполнения: Сложно управлять порядком выполнения нескольких асинхронных задач одновременно.

Чтобы решить эти проблемы, были введены промисы.

Промисы: создание, цепочки промисов

Промис (promise) – это объект, представляющий результат асинхронной операции, который может быть доступен в будущем. Он имеет три состояния:

  1. Pending (ожидание) – начальное состояние, когда выполнение операции еще не завершено.
  2. Fulfilled (выполнено) – успешное завершение операции.
  3. Rejected (отклонено) – ошибка при выполнении операции.

Пример создания простого промиса:

Преимущества промисов перед колбеками

Упрощение структуры кода: Промисы помогают избежать callback hell благодаря возможности построения цепочек вызовов.

Стандартная обработка ошибок: Ошибки автоматически перехватываются и обрабатываются блоком .catch().

Управление несколькими асинхронными операциями: Можно легко контролировать порядок выполнения нескольких промисов с помощью методов Promise.all, Promise.race и других.

Цепочка промисов

Цепочка промисов позволяет последовательно выполнять несколько асинхронных операций. Каждый метод .then() возвращает новый промис, что позволяет строить сложные последовательности действий.

Пример цепочки промисов:

Полный код, который выдаёт результат на консоли, следующий:

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

Объяснение кода

Функция fetchData. Она имитирует асинхронную операцию получения данных. Через одну секунду она разрешает промис с объектом { message: ‘Данные успешно получены’ }.

Функция processData. Она обрабатывает полученные данные. Она добавляет строку » и обработаны» к сообщению и возвращает новый объект через полторы секунды.

Функция saveToDatabase. Она имитирует сохранение данных в базу данных. Она добавляет свойство saved: true и возвращает результат через две секунды.

Функция notifyUser. Она уведомляет пользователя о результате. Она выводит сообщение в консоль через две с половиной секунды.

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

Обработка ошибок. Если на каком-то этапе возникает ошибка, она перехватывается блоком .catch() и передается в функцию handleError, которая выводит информацию об ошибке в консоль.

Параллельное выполнение промисов

Иногда нужно запустить несколько асинхронных операций параллельно и дождаться их завершения. Для этого используется метод Promise.all.

Метод Promise.all принимает массив промисов и возвращает новый промис, который выполнится тогда, когда все исходные промисы будут выполнены успешно. Если хотя бы один промис отклонен, то весь результирующий промис также будет отклонен.

Вот полный код программы:

Когда вы запустите этот скрипт, сначала начнут выполняться все три функции одновременно. Поскольку они имеют разные задержки, первая функция (fetchData) завершится первой, за ней последует вторая (fetchAdditionalData), а последней завершится третья (fetchMoreData).

Как только все три промиса разрешатся, их результаты будут собраны в массив results, и программа выведет следующее сообщение в консоль:

Пояснения:

Функции fetchData, fetchAdditionalData, fetchMoreData. Они имитируют асинхронные запросы, возвращая промисы. Каждая функция эмулирует задержку (время ожидания разное), а затем разрешает промис с сообщением о получении данных.

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

Объединение промисов с помощью Promise.all. Используется метод Promise.all, который ожидает разрешения всех промисов в массиве. Когда все три промиса разрешаются, результаты собираются в массив results.

Разбор результатов. С помощью деструктуризации массива извлекаются отдельные элементы, представляющие результаты каждого промиса, и они выводятся в консоль.

Обработка ошибок. Если хотя бы одна из функций вернет ошибку, вся операция будет прервана, и управление перейдет в блок .catch(), где ошибка будет выведена в консоль.

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

Async/Await

async/await – это синтаксический сахар, позволяющий писать асинхронный код так, будто он синхронный. Эта конструкция делает использование промисов более удобным и интуитивно понятным.

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

Пример использования async/await:

Преимущества async/await

Простота чтения и понимания: Код выглядит как обычный синхронный код, что облегчает его восприятие.

Удобство обработки ошибок: Ошибки можно обрабатывать с помощью стандартных конструкций try/catch.

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

Ограничения async/await

Использование только внутри async-функций: Ключевое слово await может использоваться только внутри функций, объявленных с async.

Ожидание всех промисов: В отличие от Promise.all, где можно ждать завершения всех промисов сразу, await ожидает каждый промис по очереди.

Давайте рассмотрим несколько примеров промисов, которые можно реализовать прямо в консоли браузера и которые выполняют полезные задачи. Я приведу код и подробное объяснение каждого примера.

Пример 1: Таймер с задержкой

Этот пример показывает, как создать промис, который разрешится через определенное количество миллисекунд.

Объяснение:

Создание промиса. Функция delay(ms) создает новый промис, который разрешается через указанное количество миллисекунд с помощью setTimeout.

Запуск промиса. Вызывая delay(2000), мы запускаем промис, который завершится через 2 секунды.

Обработка результата. Метод .then() вызывается после разрешения промиса, выводя сообщение в консоль.

Пример 3: Работа с локальным хранилищем

Этот пример демонстрирует, как использовать промисы для взаимодействия с локальным хранилищем браузера.

Объяснение:

Функции для работы с localStorage. Мы создали две функции-обертки над методами localStorage.setItem и localStorage.getItem, которые возвращают промисы. Эти функции позволяют работать с localStorage асинхронно.

Запись данных. Функция setItem принимает два параметра: ключ и значение. Значение сериализуется в строку с помощью JSON.stringify и сохраняется в localStorage. Если операция прошла успешно, промис разрешается с объектом { key, value }.

Чтение данных. Функция getItem принимает ключ и пытается прочитать соответствующее значение из localStorage. Значение десериализуется обратно в объект с помощью JSON.parse и возвращается в виде разрешения промиса.

Последовательность операций: Сначала мы записываем данные в localStorage, затем читаем их обратно и выводим результат в консоль.

Пример 4: Загрузка изображений

Этот пример показывает, как загрузить изображение с помощью промиса.

Вот HTML-файл, содержащий код для загрузки изображения с использованием промиса:

Объяснение

HTML-структура. В документе есть контейнер с идентификатором image-container, куда будет добавлено загруженное изображение.

JavaScript-код. Функция loadImage создаёт промис, который разрешает промис при успешной загрузке изображения и добавляет его в DOM, либо отказывает с ошибкой, если загрузка не удалась.

URL изображения. Указан URL https://via.placeholder.com/150, который генерирует простое изображение-заглушку размером 150×150 пикселей. Вы можете заменить его на любой другой URL изображения.

Как использовать

Скопируйте содержимое данного кода в текстовый редактор.

Сохраните файл с расширением .html, например, image-loader.html.

Откройте этот файл в любом современном браузере.

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



Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *