Разработка программного обеспечения
Объяснение промисов в JavaScript: then, catch, Promise.all и распространенные ошибки
Промисы — это то, как JavaScript обрабатывает работу, которая завершается позже, и поверх них строится async/await. Узнайте, как они ведут себя на самом деле, с реальными результатами каждого примера, и избегайте ошибок, которые поглощают ваши ошибки.
- обещание — это объект, представляющий результат, который будет доступен позже.
- Оно отложено до тех пор, пока не будет выполнено или отклонено, и однажды урегулированное оно никогда не меняется.
- Каждый
thenвозвращает новое обещание, что делает возможным цепочку. - Используйте
Promise.all, когда требуется каждый результат, иPromise.allSettled, когда вам нужен каждый результат.
Сетевые запросы, чтение файлов и таймеры завершаются позже, но JavaScript не может просто остановиться и ждать, иначе страница зависнет. Обещания JavaScript дают вам аккуратный способ сказать: «Когда это закончится, сделайте это, а если не получится, сделайте вместо этого это».
Даже если вы в основном пишете async и await, вы используете промисы внизу, поэтому их понимание значительно упрощает отладку. Мы запускали эти примеры на Node.js 24, и показанный результат получен в результате этого запуска. В современных браузерах они ведут себя так же.
Что такое обещание?
Обещание — это заполнитель для будущего значения. Подумайте о звонке, который вам вручают в ресторане, когда вы делаете заказ. У вас еще нет еды, но вы держите в руках что-то, что подскажет вам, когда она будет готова или что возникла проблема, а тем временем вы можете заниматься другими делами.
В коде асинхронная функция немедленно возвращает обещание, и вы добавляете, что должно произойти дальше:
fetch("/api/user")
.then((response) => response.json())
.then((user) => console.log(user.name))
.catch((error) => console.log("Failed:", error.message));
Три обещания гласят
| Государство | Значение | Что работает |
|---|---|---|
| Ожидается | Работа не закончена | Пока ничего |
| Выполнено | Он закончился со значением | then обработчики |
| Отклонено | Это не удалось по причине | catch обработчики |
Обещание, которое выполнено или отклонено, называется выполненным. Обещание может быть выполнено только один раз. После этого его состояние и значение фиксируются, а последующие вызовы resolve или reject игнорируются.
Создать обещание
Обычно вы используете обещания из API, таких как fetch, но создание одного показывает, как они работают. Конструктор получает две функции: resolve и reject:
function fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) {
resolve({ id, name: "Asha" });
} else {
reject(new Error("Invalid id"));
}
}, 100);
});
}
console.log(fetchUser(1));
// Promise { <pending> }
Немедленная регистрация обещания показывает, что оно все еще находится в состоянии ожидания, поскольку таймер не сработал. Значение может быть достигнуто только через обработчик или await. Всегда отклоняйте объект Error, а не строку, поскольку Error содержит трассировку стека, которая помогает при отладке.
затем поймай и, наконец,
Эти три метода присоединяют обработчики к обещанию:
fetchUser(1).then((user) => console.log(user.name));
// Asha
fetchUser(-1)
.then((user) => console.log("never runs", user))
.catch((error) => console.log("caught:", error.message))
.finally(() => console.log("finally"));
// caught: Invalid id
// finally
then выполняется в случае успеха, catch выполняется в случае сбоя и finally выполняется в любом случае. Это делает finally подходящим местом для очистки, например, для скрытия индикатора загрузки. Он не получает аргументов, поскольку его не должен волновать результат.
Объединение обещаний
Ключевая идея заключается в том, что then всегда возвращает новое обещание. Все, что возвращает ваш обработчик, становится значением нового обещания:
Promise.resolve(2)
.then((n) => n * 10)
.then((n) => n + 1)
.then((n) => console.log("chain:", n));
// chain: 21
Если обработчик возвращает другое обещание, цепочка ждет, пока оно стабилизируется, прежде чем двигаться дальше. Именно это превращает вложенные обратные вызовы в плоскую последовательность шагов:
getUser(id)
.then((user) => getOrders(user.id))
.then((orders) => getInvoice(orders[0].id))
.then((invoice) => console.log(invoice.total))
.catch(handleError);
Как ошибки перемещаются по цепочке
Отклоненное обещание пропускает каждый then, пока не достигнет catch. Выброс внутри обработчика также считается отклонением. И возвращаемый catch обычно возвращает цепочку в нужное русло:
Promise.resolve("start")
.then(() => {
throw new Error("step 2 failed");
})
.then(() => console.log("skipped"))
.catch((error) => {
console.log("caught:", error.message);
return "recovered";
})
.then((value) => console.log("continues:", value));
// caught: step 2 failed
// continues: recovered
Думайте об этом как об асинхронной версии try и catch. Один catch в конце обычно охватывает всю цепочку. Если вы хотите зарегистрировать только ошибку и все равно потерпеть неудачу, создайте ее еще раз изнутри catch.
Совет: fetch отклоняется только при сбоях в сети. Ответ 404 или 500 по-прежнему выполняется, поэтому проверьте response.ok самостоятельно. В нашей шпаргалке по кодам статуса HTTP объясняется, что означает каждый код.
Promise.all, allSettled, раса и любые
Эти статические методы координируют сразу несколько промисов. Все они принимают массив и возвращают одно обещание.
| Метод | Выполняется, когда | Отклоняет, когда | Используйте его для |
|---|---|---|---|
Promise.all |
Все выполнить | Любой отвергает | Все результаты обязательны |
Promise.allSettled |
Все решено | Никогда | Вы хотите каждого результата |
Promise.race |
Первый, кто поселяется, выполняет | Первый, кто решает проблемы с браком | Таймауты |
Promise.any |
Первый выполняет | Все отклонить | Первый успешный источник |
Чтобы протестировать их, мы использовали два небольших помощника: один завершается успешно после задержки, а другой завершается сбоем после задержки:
const wait = (ms, value) =>
new Promise((resolve) => setTimeout(() => resolve(value), ms));
const fail = (ms, message) =>
new Promise((_, reject) => setTimeout(() => reject(new Error(message)), ms));
Promise.all
await Promise.all([wait(300, "a"), wait(100, "b"), wait(200, "c")]);
// [ 'a', 'b', 'c' ] took about 300 ms
Две детали, на которые стоит обратить внимание. Результаты сохраняют порядок ввода, хотя b закончил первым. И общее время составило около 300 мс, время самой медленной задачи, а не сумма 600 мс, потому что все три уже работали одновременно. Если кто-нибудь из них отвергнет, то сразу отвергнет все:
await Promise.all([wait(100, "a"), fail(50, "b failed")]);
// rejected: b failed
Promise.allSettled
Он ожидает всего и сообщает о каждом результате, поэтому одна ошибка не теряет остальных. Он подходит для панели мониторинга, где каждый виджет загружается независимо:
const results = await Promise.allSettled([wait(50, "a"), fail(50, "b failed")]);
results.map((r) => r.status);
// [ 'fulfilled', 'rejected' ]
Promise.race и Promise.any
await Promise.race([wait(300, "slow"), wait(100, "fast")]); // fast
await Promise.any([fail(50, "x"), wait(100, "ok")]); // ok
Разница в том, что race принимает решение первым, даже если оно не удалось, тогда как any игнорирует неудачи, пока что-то удается. Классическое использование race — добавление тайм-аута к медленному запросу, хотя более медленная задача продолжает выполняться в фоновом режиме, пока вы не отмените ее.
Обещания и async/await
async и await — более читабельный способ работы с теми же промисами. Функция async всегда возвращает обещание, а await приостанавливает выполнение этой функции до тех пор, пока обещание не будет выполнено:
async function showInvoice(id) {
try {
const user = await getUser(id);
const orders = await getOrders(user.id);
console.log(orders.length);
} catch (error) {
console.log("Failed:", error.message);
}
}
Отклонения становятся выброшенными ошибками, поэтому работают обычные try и catch. Вам все равно понадобится Promise.all для параллельной работы, потому что ожидание одного вызова за другим запускает их последовательно. В нашем руководстве по асинхронному/ожидающему использованию JavaScript эта тема рассматривается более подробно.
Обещания и цикл событий
Обработчики промисов никогда не вызываются немедленно, даже если промис уже выполнен. Они запускаются после завершения текущего кода и до таймеров. Вот доказательство:
setTimeout(() => console.log("timeout"), 0);
Promise.resolve().then(() => console.log("microtask"));
console.log("sync");
// sync
// microtask
// timeout
Синхронный код выполняется первым. Далее идет очередь микрозадач, в которой хранятся обработчики обещаний. Только после этого цикл событий выбирает следующую задачу, например таймер. Это популярный вопрос на собеседованиях, и он объясняет, почему нулевой результат setTimeout не является немедленным.
Обещания против обратных вызовов
| Беспокойство | Обратные вызовы | Обещания |
|---|---|---|
| Секвенирование | Вложенные, глубже на каждом этапе | Плоская цепь |
| Ошибки | Проверено на каждом уровне | Один catch может перекрыть цепочку |
| Параллельная работа | Ручные счетчики | Promise.all |
| Позвонили дважды по ошибке? | Возможно | Невозможно рассчитаться дважды |
Распространенные ошибки
- Забываем вернуться внутрь
then. Безreturnследующий шаг получаетundefinedи не ждет вашего внутреннего обещания. - Нет
catchвообще. Это приводит к необработанному отклонению, а в Node.js это останавливает процесс по умолчанию. - Вложение вместо цепочки. Помещение
thenвнутрьthenвоссоздает ад обратного вызова. Верните обещание и продолжите цепочку. - Обертывание обещания в
new Promise. Если функция уже возвращает обещание, просто используйте его. - Использование
forEachс асинхронными обратными вызовами. Он не ждет. Используйтеfor...ofилиPromise.allсmap, как показано в нашем руководстве по методам массивов JavaScript. - Думаю, что обещание можно отменить. Само по себе оно не может быть отменено. Для запросов используйте
AbortController.
Итоги
Промисы сводятся к нескольким правилам: обещание выполняется один раз, then возвращает новое обещание, а ошибки перемещаются по цепочке, пока что-то их не перехватит. Как только эти правила станут естественными, async и await перестанут быть волшебством. Для практики перепишите одну функцию обратного вызова, используя промисы, затем получите две конечные точки с помощью Promise.all и правильно обработайте ошибку.
Часто задаваемые вопросы
Что такое обещание в JavaScript?
Обещание — это объект, представляющий конечный результат асинхронной работы. Он начинается как ожидающий, затем либо выполняется со значением, либо отклоняется с указанием причины.
В чем разница между thenи catch?
затем запускается, когда обещание выполняется, и catch запускается, когда оно отклоняется. В этом случае catch фактически использует только обработчик отклонения, а также может перехватывать ошибки, возникшие на более ранних этапах.
В чем разница между Promise.all и Promise.allSettled?
Promise.all отклоняется, как только отклоняется одно обещание. Promise.allSettled всегда ожидает их всех и сообщает о статусе, значении или причине каждого из них.
Должен ли я использовать обещания или async/await?
Они работают вместе, поскольку ожидание основано на обещаниях. Большая часть кода лучше всего читается с помощью async/await, тогда как Promise.all и подобные методы остаются полезными для параллельной работы.
Обещания выполняются параллельно?
JavaScript выполняет по одному фрагменту кода в потоке, но работает так, как будто сетевые запросы выполняются в фоновом режиме. Несколько из них могут выполняться одновременно.
Продолжайте учиться
Нужна помощь с тестированием, WordPress или SEO?
Мы помогаем командам выпускать лучшее программное обеспечение и развивать свои сайты. Расскажите нам, над чем вы работаете, и мы предложим четкий следующий шаг.
Больше руководств по тестирование, искусственный интеллект и современные разработки
Понравилась эта статья? Выберите тему и сразу приступайте к нашим новейшим руководствам.
Объяснение галлюцинаций искусственного интеллекта: почему языковые модели все выдумывают и как их уменьшить
Галлюцинации ИИ объяснили: что это такое, почему языковые модели их создают, распространенные типы, как уменьшить их с помощью заземления и подсказо…
Читать статью →DevOpsОбъяснение переменных среды: файлы .env, секреты и правильно выполненная конфигурация
Переменные среды объясняются на примерах Python, которые мы запускали: их чтение и установка, почему каждое значение является строкой, файлы .env, н…
Читать статью →Разработка ПООбъяснение ограничения скорости API: алгоритмы, заголовки и обработка ошибок 429
Ограничение скорости API объясняется с помощью проведенного нами моделирования: сегмент токенов, фиксированное окно, скользящее окно, 429 ответов, з…
Читать статью →Автоматизация тестированияИздевательство в Python: unittest.mock, патч и ошибки, из-за которых тесты лгут
Издевательство на Python с помощью unittest.mock, протестировано: Mock и return_value, Side_effect, исправление использования имени, спецификация и …
Читать статью →Тестирование ПООбъяснение покрытия кода: покрытие линий или ветвей и что на самом деле означает 100%
Покрытие кода объяснено с помощью реального запуска покрытия.py: покрытие строк, ветвей и условий, чтение отчета, почему 100%-ное покрытие все еще м…
Читать статью →Базы данных и SQLDELETE, TRUNCATE и DROP в SQL: различия, откат и когда использовать каждый из них
Объяснение DELETE, TRUNCATE и DROP на примере протестированного SQL: что каждый из них удаляет, откат, триггеры, сброс идентификаторов, скорость и ч…
Читать статью →Базы данных и SQLОбъяснение представлений SQL: создавайте, используйте и избегайте ловушек, с примерами
Представления SQL, объясненные с помощью запросов, которые мы выполняли: создание представления, результаты в реальном времени, скрытие столбцов, пр…
Читать статью →Разработка ПОИнтерфейс TypeScript и тип: реальные различия, протестированные с помощью компилятора
Интерфейс TypeScript против типа, проверенный реальным компилятором: что могут делать оба, что может делать только один, слияние объявлений, расшире…
Читать статью →Разработка ПООбъяснение Python *args и **kwargs: гибкие функции с понятными примерами
Python *args и **kwargs, объясненные с помощью кода, который мы запускали: упаковка и распаковка, порядок параметров, аргументы только по ключевым с…
Читать статью →Разработка ПОЛямбда-функции Python: синтаксис, реальное использование и когда их не следует использовать
Лямбда-функции Python, объясненные с помощью кода, который мы запускали: синтаксис, сортировка с помощью ключа, карты и фильтра, замыкания, ловушка …
Читать статью →Искусственный интеллектЧто такое ИИ-агенты? Как они работают: можно запустить простой цикл агента
Что такое агенты ИИ? Простое руководство по циклу агента, инструментам, памяти и ограничениям, с небольшим циклом Python, который мы запустили, реал…
Читать статью →DevOpsМониторинг и наблюдаемость: реальная разница с журналами, метриками и трассировками
Объяснение мониторинга и наблюдаемости: известные и неизвестные сбои, журналы, метрики и трассировки, пошаговое руководство по инцидентам при оформл…
Читать статью →DevOpsDocker Volumes против Bind Mounts: правильное сохранение данных контейнера
Объяснение томов Docker и монтирования привязки: почему исчезают данные контейнера, именованные тома, монтирование привязки, tmpfs, примеры создания…
Читать статью →Автоматизация тестированияTDD против BDD: ключевые различия, одна функция, созданная для обоих способов, и когда использовать каждый
TDD и BDD объясняются с помощью одной функции скидки, созданной в обоих направлениях: красно-зеленый рефакторинг с кодом, который мы запускали, сцен…
Читать статью →Тестирование ПОПользовательское приемочное тестирование (UAT): процесс, контрольный список и реальные примеры
Приемочное тестирование пользователей объяснило: кто его проводит, чем оно отличается от контроля качества, семиэтапный процесс, шаблон тестового сц…
Читать статью →Базы данных и SQLОбъяснение значений NULL в SQL: IS NULL, COALESCE, NULLIF и ловушки, которых следует избегать
Значения SQL NULL объяснены с помощью запросов, которые мы выполнили: трехзначная логика, IS NULL, COALESCE, NULLIF, как агрегаты, соединения и сорт…
Читать статью →Базы данных и SQLОператор SQL CASE WHEN: синтаксис, реальные примеры и распространенные ошибки
SQL CASE WHEN объяснен с помощью запросов, которые мы выполняли: простые формы и формы для поиска, порядок условий, повороты с помощью SUM, трюки CO…
Читать статью →Разработка ПОЧто такое JWT? Веб-токены JSON, объясненные на рабочем примере
Что такое JWT? Объяснение JSON Web Tokens: три части: токен, созданный и измененный в коде, который мы запускали, JWT и сеансы, хранилище, токены об…
Читать статью →Разработка ПООбъяснение цикла событий JavaScript: стек вызовов, микрозадачи и таймеры с примерами
Цикл событий JavaScript объясняется с помощью кода, который мы запускали: стек вызовов, очереди задач и микрозадач, почему setTimeout 0 не является …
Читать статью →Разработка ПОГенераторы Python и объяснение доходности: экономьте память с помощью реальных примеров
Генераторы Python и доходность, объясненные с помощью кода, который мы запускали: как они приостанавливаются и возобновляются, измеряют экономию пам…
Читать статью →Искусственный интеллектЧто такое вложения? Простое объяснение векторов и косинусного сходства
Что такое вложения? Простое руководство по векторам, косинусному подобию и семантическому поиску с небольшим примером, который мы использовали, общи…
Читать статью →DevOpsСине-зеленое и канареечное развертывание: различия, компромиссы и как выбрать
Объяснение сине-зеленого и канареечного развертывания: как каждый из них работает, стоимость, риски и откат, стабильное группирование пользователей,…
Читать статью →Автоматизация тестированияНенадежные тесты: что их вызывает и как их исправить навсегда
Ненадежные тесты объясняются на демонстрациях, которые мы запускали: основные причины, способы подтверждения и диагностики, правильные исправления в…
Читать статью →Тестирование ПОНагрузочное тестирование и стресс-тестирование: различия, метрики и примеры k6
Объяснение нагрузочного и стресс-тестирования: что доказывает каждое из них, тесты на всплески и выдержки, важные метрики, почему средние значения в…
Читать статью →Тестирование ПОТестовый пример и тестовый сценарий: ключевые различия, примеры и когда использовать каждый
Объяснение тестового случая и тестового сценария: четкие определения, параллельная таблица, одна функция сброса пароля, взятая из сценариев в случаи…
Читать статью →Базы данных и SQLОптимизация SQL-запросов: чтение планов выполнения и исправление медленных запросов с указанием времени
Оптимизация SQL-запросов проверена на миллионе строк: читайте планы выполнения, добавляйте правильные индексы, исправляйте неразмещаемые фильтры, ис…
Читать статью →Базы данных и SQLSQL UNION против UNION ALL: различия, примеры и проверка производительности
SQL UNION и UNION ALL объяснены с помощью запросов, которые мы выполнили: дубликаты, правила, которым следуют оба, реальная ошибка данных, измеренна…
Читать статью →Разработка ПОУчебное пособие по API JavaScript Fetch: GET, POST, обработка ошибок и тайм-ауты
Учебное пособие по API JavaScript Fetch, протестированное на локальном сервере: GET и POST, почему не выдается ошибка 404, многоразовый помощник, ст…
Читать статью →Разработка ПООбработка файлов Python: чтение, запись, CSV, JSON и pathlib с примерами
Обработка файлов Python объясняется с помощью кода, который мы запускали: открытые режимы, чтение больших файлов, CSV, JSON, pathlib, двоичные файлы…
Читать статью →Разработка ПОКлассы и объекты Python: учебник ООП с реальными примерами
Классы и объекты Python, объясненные с помощью кода, который мы запускали: __init__, методы, атрибуты класса и экземпляра, наследование, свойства, м…
Читать статью →Искусственный интеллектЧто такое большая языковая модель? Как работают LLM, объяснено без шумихи
Что такое большая языковая модель? LLM объясняется простым языком: токены, встраивания, внимание, обучение, температурные и контекстные окна, крошеч…
Читать статью →DevOpsКоманды Linux для начинающих: основное руководство по терминалу с примерами
Команды Linux для начинающих, сгруппированные по задачам: навигация, файлы, просмотр журналов, grep, каналы, разрешения, процессы и архивы, с практи…
Читать статью →Автоматизация тестированияSelenium vs Playwright: ключевые различия, сравнение кода и что выбрать
Сравнение Selenium и Playwright: архитектура, ожидание, поддержка браузера и языка, отладка, одинаковый тест в обоих инструментах, советы по миграци…
Читать статью →Тестирование ПОРазделение эквивалентности: метод проектирования тестов, объясненный на рабочих примерах
Объяснение эквивалентного разделения: как найти действительные и недействительные разделы, рабочие примеры для возрастов, паролей и купонов, проведе…
Читать статью →Тестирование ПОСерьезность и приоритет в тестировании программного обеспечения: различия, матрица и реальные примеры
Объяснение серьезности и приоритета при тестировании программного обеспечения: четкие определения, четырехсторонняя матрица с примерами, шкалы уровн…
Читать статью →Базы данных и SQLУчебное пособие по подзапросам SQL: типы, коррелирующие подзапросы и EXISTS с примерами
Руководство по подзапросам SQL с выполненными нами запросами: скалярные подзапросы, IN, FROM и SELECT, коррелированные подзапросы, EXISTS, ловушка N…
Читать статью →Базы данных и SQLОбъяснение транзакций SQL и ACID: COMMIT, ROLLBACK и уровни изоляции
Транзакции SQL и ACID, объясненные с помощью банковского перевода, который мы выполнили: COMMIT, ROLLBACK, точки сохранения, блокировки, уровни изол…
Читать статью →Разработка ПОGit Commands Cheat Sheet: The Commands You Actually Use, With Examples
Шпаргалка по командам Git, сгруппированная по задачам: настройка, промежуточный этап, ветки, удаленные устройства, тайник, история и исправление оши…
Читать статью →Разработка ПООбъяснение промисов в JavaScript: then, catch, Promise.all и распространенные ошибки
Обещания JavaScript, объясненные с помощью кода, который мы запускали: состояния, цепочки then и catch, обработка ошибок, Promise.all, allSettled, R…
Читать статью →Разработка ПОУчебное пособие по словарю Python: методы, примеры и общие шаблоны
Учебное пособие по словарю Python с кодом, который мы выполнили: создание, чтение и обновление словарей, ключевые методы, слияние, понимание, шаблон…
Читать статью →Искусственный интеллектЧто такое РАГ? Генерация с расширенным поиском, объясненная на рабочем примере
Что такое РАГ? Retrieval-augmented generation explained step by step: indexing, chunking, embeddings, retrieval, a small working Python example, eva…
Читать статью →DevOpsУчебное пособие по Terraform для начинающих: инфраструктура как код от первого файла до модулей
Учебное пособие по Terraform для начинающих: начните без облачной учетной записи, изучите инициализацию, планирование и применение, затем переменные…
Читать статью →DevOpsDocker и Kubernetes: ключевые различия, как они работают вместе и что вам нужно
Объяснение Docker и Kubernetes: что делает каждый из них, одно и то же приложение работает с обоими, сравнение Docker Compose и Swarm, изменение doc…
Читать статью →Автоматизация тестированияУчебное пособие по Pytest для начинающих: фикстуры, параметризация и реальные примеры
Учебное пособие по pytest для начинающих с тестами, которые мы запускали на pytest 9.2: утверждения, фикстуры, параметризация, conftest, маркеры, tm…
Читать статью →Тестирование ПОЖизненный цикл тестирования программного обеспечения (STLC): 6 этапов, критерии входа и выхода, пример
Объясняется жизненный цикл тестирования программного обеспечения: шесть этапов STLC с критериями входа и выхода, результаты, пример проработанной фу…
Читать статью →Разработка ПОGit merge vs rebase: отличия, примеры и когда что использовать
Git merge vs rebase с реальным выводом log, сравнительной таблицей, золотым правилом rebase и безопасным рабочим процессом для команды.
Читать статью →Разработка ПОГенераторы списков в Python: синтаксис, примеры и когда их избегать
Генераторы списков в Python: синтаксис, фильтры, if-else, вложенные циклы, измеренное время и случаи, когда обычный цикл for лучше.
Читать статью →Базы данных и SQLSQL JOIN: объяснение с примерами (Inner, Left, Right, Full)
Разберите SQL JOIN шаг за шагом на примерах таблиц и запросов: INNER, LEFT, RIGHT, FULL, CROSS и SELF, а также частые ошибки и советы по производите…
Читать статью →