В JavaScript промис — это такая штука, которая обещает вернуть результат какого-то действия, но не сразу, а когда оно закончится. Промисы нужны, чтобы работать с асинхронными задачами — например, загрузкой данных с сервера, чтением файлов или чем-то ещё, что занимает время. Пока промис в ожидании, остальной код спокойно выполняется дальше и не зависает.
Сегодня разберёмся, как это всё работает, как правильно обрабатывать промисы и почему без них жизнь разработчика была бы сложнее.
Введение в промисы
Когда мы пишем код на JavaScript, многие операции занимают время: загрузка данных с сервера, таймеры, запросы к API. Если бы сайт зависал, пока не придут данные, было бы плохо. Поэтому в JS есть механизмы, позволяющие запускать долгие процессы и при этом не тормозить выполнение остального кода. Один из таких инструментов — промисы (Promise).
Что такое промисы
Promise — это объект, который представляет результат асинхронной операции: либо успешный результат, либо ошибку. Проще говоря, это обещание, что код когда-нибудь выполнится и выдаст результат.
Промис может находиться в одном из трёх состояний:
pending
— ожидание (промис ещё не завершился);fulfilled
— успешно завершён;rejected
— ошибка.
Допустим, вы зашли в кофейню и заказали кофе. Бариста принимает заказ, но не выдаёт вам кофе сразу — его ещё нужно приготовить. Поэтому он даёт вам чек и тем самым обещает, что вы получите какой-то результат. Пока кофе готовится, вам необязательно быть у стойки — вы можете сесть за стол и почитать новую статью в «Коде». То есть, пока идёт ожидание, вы можете асинхронно выполнять другие задачи. И пока вы ждёте, происходит следующее:
- Кофе в процессе приготовления →
pending
(ожидание). - Бариста ставит перед вами чашку →
fulfilled
(успех, заказ выполнен). - Ой, кофемашина взорвалась или кто-то утащил все зёрна →
rejected
(отказ, заказ не выполнен).
Промис в JavaScript работает по такому же принципу: он создаётся, ожидает результат и в итоге либо выполняется, либо завершается с ошибкой.
Теперь разберёмся, как создать такой объект в JavaScript.







Создание промисов
Промисы создаются с помощью конструктора Promise
, который позволяет определить асинхронную операцию и её возможные исходы.
Конструктор Promise
Когда мы создаём новый промис, ему передаётся функция-исполнитель (executor), которая сразу же запускается. Эта функция принимает два аргумента:
resolve
— вызывает успешное выполнение промиса (fulfilled);reject
— сообщает об ошибке (rejected).
Синтаксис такой:
// Создаём через конструктор новый промис
const myPromise = new Promise((resolve, reject) => {
// Здесь выполняется асинхронная операция
if (успешное выполнение) {
resolve(результат);
} else {
reject(ошибка);
}
});
Дальше мы напишем функцию с промисом: посмотрим, как он работает изнутри, и правильно обработаем его результаты.
Синтаксис создания промиса
Допустим, у нас есть функция orderCoffee()
, которая имитирует заказ кофе в кафе. Мы не знаем заранее, чем всё закончится: кофе может быть приготовлен успешно либо что-то пойдёт не так и сломается кофемашина.
Вот как это будет выглядеть в коде:
function orderCoffee() {
console.log("Заказ принят! Ваш кофе готовится...");
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < 0.8) {
resolve("Ваш заказ готов!");
} else {
reject("Извините, у нас сломалась кофемашина");
}
// Имитация задержки в 2 секунды
}, 2000);
});
}
Вот что мы здесь делаем:
- Создаём и сразу возвращаем новый Promise.
- Внутри промиса используем
setTimeout()
, чтобы имитировать задержку на приготовление. - Через 2 секунды случайным образом решаем, успешно ли приготовлен кофе (
resolve
) или произошла ошибка (reject
). В нашем условии с рандомом прописано, что кофе приготовится успешно с вероятностью 80%.
Если мы просто вызовем orderCoffee()
, то увидим в консоли только «Заказ принят! Ваш кофе готовится…», но сам промис останется в ожидании (pending
).

Чтобы получить результат, нужно добавить обработчики .then()
и .catch()
. Сделаем это в следующем разделе.
Методы промисов
Когда асинхронная операция завершается, промис из состояния pending
переходит в состояние успеха или ошибки.

Для обработки этих результатов используются методы then()
, catch()
и finally()
.
Методы then и catch
Метод then()
может принимать два аргумента и обработать как успех, так и ошибку промиса. Но хорошей практикой считается задавать в методе then()
действия только для успешного завершения.
Напишем обработчик успешного события промиса. Когда мы писали промис, то передали ему аргумент функции-исполнителя resolve
с соответствующим сообщением «Ваш заказ готов!». Теперь передадим это сообщение в первый обработчик в цепочке then()
:
.then((result) => {
console.log(result);
})
Для обработки состояния ошибки промиса используется метод catch()
, который получает один аргумент, представляющий ошибку:
.catch((error) => {
console.error(error);
});
Мы написали два обработчика, теперь добавим их к вызову нашей функции orderCoffee()
:
orderCoffee()
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error(error);
});
Вызовем функцию вместе с обработчиками. Получаем первое сообщение, а затем ждём, пока выполняется асинхронная операция. Спустя 2 секунды в консоль выводится результат:

Метод finally
Есть ещё метод finally()
, и его можно использовать для выполнения кода, который должен быть исполнен после завершения промиса независимо от его разрешения или отклонения. Он полезен для выполнения заключительных действий в программе: очистки ресурсов, закрытия файловых потоков или скрытия индикаторов загрузки в пользовательском интерфейсе.
В нашем примере finally()
мог бы выглядеть так:
orderCoffee()
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error(error);
})
.finally(() => {
console.log("Актуальные акции ищите в нашем приложении!");
});

Часто все три метода объединяются в цепочки методов. Это позволяет создавать сложные последовательности асинхронных операций, где каждая следующая зависит от результата предыдущей. Об этом поговорим чуть позже.
Статические методы: Promise.all(), Promise.allSettled(), Promise.any(), Promise.race()
Статические методы промисов позволяют работать сразу с несколькими асинхронными операциями. Они помогают дождаться выполнения всех промисов, хотя бы одного или первого завершённого.
👉 Кстати, на собеседованиях часто спрашивают, как вручную реализовать тот или иной статический метод промиса, то есть написать полифил. Поэтому полезно разобраться, как под капотом работают эти методы.
Promise.all()
Ожидает выполнения всех промисов и возвращает массив их результатов. Если хоть один промис завершился с ошибкой, весь Promise.all()
переходит в состояние rejected
. Этот метод используют, когда все операции зависят друг от друга и нужна только полная успешная загрузка.
// Создаём три промиса, которые имитируют приготовление напитков
// Чай готовится 1 секунду
const p1 = new Promise(res => setTimeout(() => res("Чай"), 1000));
// Кофе готовится 1,5 секунды
const p2 = new Promise(res => setTimeout(() => res("Кофе"), 1500));
// Сок выжимается за 2 секунды
const p3 = new Promise(res => setTimeout(() => res("Сок"), 2000));
// Используем Promise.all(), чтобы дождаться завершения всех промисов
Promise.all([p1, p2, p3])
// Выведет массив с готовыми напитками
.then(results => console.log("Все напитки готовы:", results))
// Если хотя бы один промис завершится с ошибкой, сработает catch()
.catch(error => console.error("Ошибка:", error));

Если хотя бы один промис завершится с reject, Promise.all()
сразу перейдёт в catch
и проигнорирует остальные.
Promise.allSettled()
Ждёт выполнения всех промисов, но неважно, успешно или с ошибкой. Возвращает массив с результатами каждого промиса, где указано status: fulfilled
или status: rejected
.
Используют, когда нужно обработать все промисы, независимо от их результата.
const p1 = new Promise(res => setTimeout(() => res("Чай"), 1000));
const p2 = new Promise((_, rej) => setTimeout(() => rej("Кофе не получился"), 1500));
const p3 = new Promise(res => setTimeout(() => res("Сок"), 2000));
Promise.allSettled([p1, p2, p3])
.then(results => console.log(results));
В результате получаем массив:

В отличие от Promise.all()
, ошибки не прерывают выполнение, а просто возвращаются в reason
.
Promise.any()
Ожидает первый успешно выполненный промис и возвращает его результат. Если все промисы завершились ошибками, возвращает массив ошибок.
Используют, когда достаточно любого успешного результата.
const p1 = new Promise((_, rej) => setTimeout(() => rej("Ошибка 1"), 1000));
const p2 = new Promise(res => setTimeout(() => res("Успех 2"), 1500));
const p3 = new Promise((_, rej) => setTimeout(() => rej("Ошибка 3"), 2000));
Promise.any([p1, p2, p3])
.then(result => console.log("Первый успешный:", result))
.catch(error => console.error("Все промисы зафейлились:", error));

Если все промисы завершатся с ошибкой, Promise.any()
вернёт AggregateError
(специальный объект, содержащий все ошибки).
Promise.race()
Ждёт первый завершившийся промис из списка и передаёт его результат дальше. Неважно, был ли этот промис успешным (fulfilled) или завершился с ошибкой (rejected) — Promise.race()
просто берёт первый и завершает выполнение.
Используется в случаях, когда важна скорость — например, при установке тайм-аута для запроса или выборе первого пришедшего ответа из нескольких источников.
const p1 = new Promise(res => setTimeout(() => res("Успех 1"), 2000));
const p2 = new Promise((_, rej) => setTimeout(() => rej("Ошибка 2"), 1000));
const p3 = new Promise(res => setTimeout(() => res("Успех 3"), 1500));
Promise.race([p1, p2, p3])
.then(result => console.log("Первый завершился:", result))
.catch(error => console.error("Ошибка первой операции:", error));
В этом примере промис p2
завершается через 1 секунду с ошибкой, и поскольку он быстрее всех, то Promise.race()
сразу завершится с rejected
:

Но если бы первым завершился успешный промис, то результат пошёл бы в then()
.
👉 Promise.race()
не дожидается остальных промисов, а просто берёт первый завершившийся. Если важно получить все результаты, даже если некоторые завершились с ошибкой, лучше использовать Promise.allSettled()
.
Цепочки промисов
Часто then
, catch
, finally
объединяются в цепочки методов. Это позволяет создавать сложные последовательности асинхронных операций, где каждая следующая зависит от результата предыдущей.
Передача данных по цепочке промисов
Каждый then()
получает данные от предыдущего промиса и передаёт их дальше.
Допустим, бариста сначала принимает заказ, затем готовит кофе, добавляет сахар и в конце подаёт кофе клиенту — без успешного выполнения предыдущей операции следующая невозможна:
takeOrder()
.then(order => {
console.log(`Заказ принят: ${order}`);
// Готовим кофе
return makeCoffee(order);
})
.then(coffee => {
console.log(`Кофе готов: ${coffee}`);
// Добавляем сахар
return addSugar(coffee);
})
.then(sweetCoffee => {
console.log(`Добавлен сахар: ${sweetCoffee}`);
// Подаём клиенту
return serveCoffee(sweetCoffee);
})
.then(finalDrink => {
console.log(`Подано клиенту: ${finalDrink}`);
})
.catch(error => {
console.error("Что-то пошло не так:", error);
});
Такой подход делает код читаемым и последовательным, без сложных вложенностей — сразу понятно, что за чем идёт. С промисами мы поэтапно описываем процесс, а ошибки ловятся в одном catch()
, что упрощает отладку.
Обработка ошибок в цепочках
Иногда в процессе выполнения одной из операций может произойти ошибка. В нашей аналогии с кофейней — сломалась кофемашина или закончился сахар. Если ошибка не обработана, то вся цепочка просто сломается.
orderCoffee()
// Движемся по цепочке промисов
.then(coffee => {
console.log(`Получен заказ: ${coffee}`);
return addSugar(coffee);
})
.then(sweetCoffee => {
console.log(`Добавлен сахар: ${sweetCoffee}`);
return serveCoffee(sweetCoffee);
})
.then(finalDrink => {
console.log(`Подано клиенту: ${finalDrink}`);
})
// Если где-то ошибка, то код переходит сюда
.catch(error => {
console.error("Что-то пошло не так:", error);
})
// Выполняется в любом случае
.finally(() => {
console.log("Рабочий день бариста продолжается...");
});
Суть в следующем:
catch()
перехватит ошибку из любого предыдущегоthen
.- Если ошибка случится на любом этапе (orderCoffee(), addSugar(), serveCoffee()), выполнение цепочки остановится, и код перейдёт в
catch()
. finally()
выполнится в любом случае — независимо от успеха или ошибки.
Промисы в реальной разработке
Промисы используются везде, где нужно дождаться результата асинхронной операции: запросы к серверу, работа с файлами, задержки в анимациях. Они делают код чище и удобнее, а большинство современных API уже работают на промисах.
Сценарии могут быть такими:
- Запросы на сервер (fetch, axios) — самый частый кейс. Любая загрузка данных с API, отправка форм или обновление информации на странице требуют асинхронного запроса, чтобы не блокировать работу приложения.
- Чтение и обработка файлов — если пользователю нужно загрузить изображение или обработать данные из файла, это тоже происходит асинхронно. В браузере это реализовано через File API, а в Node.js — через модуль fs.
- Таймеры и анимации — плавные эффекты, задержки перед выполнением действия, работа с setTimeout и requestAnimationFrame. Например, можно показать анимацию загрузки перед тем, как появятся данные.
- Работа с IndexedDB — браузерное хранилище данных, где можно сохранять кэшированные файлы или пользовательские настройки. Операции с базой происходят асинхронно, и промисы помогают удобно их обрабатывать.
- Взаимодействие с устройствами — промисы используются при работе с Bluetooth, Geolocation API (получение местоположения пользователя), WebRTC (передача видео и аудио в реальном времени) и другими API.
Дальше посмотрим реальный пример.
Пример использования промисов
Допустим, у нас есть погодное приложение, которое получает температуру в нужном городе. Код, если что, рабочий, можете потестить со своим городом:
function getWeather(city) {
const apiKey = "your-api-key";
// Формируем URL с нужным городом и ключом API
const apiUrl = `https://api.weatherapi.com/v1/current.json?key=${apiKey}&q=${city}`;
// Делаем асинхронный запрос на сервер
return fetch(apiUrl)
.then(response => {
// Проверяем, не вернул ли сервер ошибку
if (!response.ok) {
// Если да, то вызываем ошибку
throw new Error("Ошибка при получении данных");
}
// Преобразуем ответ в JSON
return response.json();
})
.then(data => {
// Достаём из ответа нужные данные: город, температуру и описание погоды
return `Температура в ${data.location.name}: ${data.current.temp_c}°C, ${data.current.condition.text}`;
});
}
// Используем функцию и обрабатываем промис
getWeather("Hanoi")
// Если запрос успешен, выводим данные в консоль
.then(result => console.log(result))
// Если произошла ошибка, выводим её в консоль
.catch(error => console.error("Ошибка:", error));
Здесь происходит следующее:
- Отправляем HTTP-запрос с помощью
fetch()
, который возвращает промис. - Когда сервер отвечает, промис разрешается, и в
then()
мы получаем объектresponse
. - Проверяем
response.ok
: - Если сервер вернул успешный ответ (код 200–299), превращаем его в JSON и передаём дальше.
- Если сервер вернул ошибку (404, 500 и так далее), создаём исключение с
throw new Error()
, и код переходит вcatch()
. - Если произошла сетевая ошибка (например, нет интернета, сервер недоступен),
fetch()
сразу переходит вcatch()
, минуяthen()
. - В
catch()
обрабатываем все возможные ошибки, чтобы код не сломался. - Вызываем функцию и выводим результат в консоль.
Такой код не блокирует интерфейс, потому что fetch()
работает асинхронно, мы можем легко подставить любой город, не меняя код. А если возникнет ошибка, то сработает catch()
, и приложение не упадёт.
Распространённые ошибки
Хотя промисы делают код чище и удобнее, неправильное их использование может привести к багам и проблемам. Разберём несколько типичных ошибок.
🚫 Не обрабатывать ошибки промисов
Если промис завершится с ошибкой, но при этом не добавлен метод catch()
, браузер покажет предупреждение UnhandledPromiseRejectionWarning
, а в продакшене это может привести к падению сервера.
//❌ Плохо
fetch("https://example.com/api")
// Ошибки не обрабатываются!
.then(response => response.json());
// ✅ Хорошо
fetch("https://example.com/api")
.then(response => response.json())
.catch(error => console.error("Ошибка запроса:", error));
🚫 Использовать try/catch внутри промиса
Некоторые разработчики помещают try/catch
внутрь new Promise()
, но это не имеет смысла. Промис уже умеет перехватывать ошибки и передавать их в catch()
, так что лишний try/catch
только усложняет код.
// ❌ Неправильно
new Promise((resolve, reject) => {
try {
const data = someFunction();
resolve(data);
} catch (e) {
reject(e);
}
})
.then(data => console.log(data))
.catch(error => console.error(error));
// ✅ Правильно
new Promise((resolve, reject) => {
const data = someFunction();
// Ошибка и так пойдёт в catch()
resolve(data);
})
.then(data => console.log(data))
.catch(error => console.error(error));
🚫 Неправильно использовать Promise.race()
Promise.race()
возвращает результат самого первого выполненного промиса, но если не обработать reject()
, это может привести к пропущенным ошибкам.
В этом коде:
Promise.race([
fetch("https://slow.api.com"),
fetch("https://fast.api.com"),
]).then(response => console.log(response));
Если один промис завершается первым, результат этого промиса передаётся в then()
, а второй промис не обрабатывается вообще.
Если первый промис (fast.api.com) успешный, но второй (slow.api.com) завершится с ошибкой, мы её не увидим. Promise.race()
просто игнорирует остальные промисы после первого завершённого. Если оба промиса ошибочные, но один завершается быстрее, будет обработана только его ошибка. Вторая ошибка не попадёт в catch()
.
Поэтому если важно видеть все результаты, то лучше использовать метод Promise.allSettled()
, который ждёт все промисы и возвращает их статусы:
Promise.allSettled([
fetch("https://slow.api.com"),
fetch("https://fast.api.com"),
]).then(results => console.log(results));
Сравнение промисов с другими асинхронными паттернами
Функции обратного вызова
Один из способов работы с асинхронностью — использовать обратные вызовы (колбэки, callbacks). В этом случае одна функция передаётся в другую в виде аргумента и выполняется позже — после того как завершится какая-то асинхронная операция.
Но с обратными вызовами иногда возникает проблема: они могут сделать код сложным и неподдерживаемым. Если в коде много вложенных колбэков, то возникает то, что программисты называют адом обратных вызовов. Вот пример такой ситуации:
getData(function(a){
getMoreData(a, function(b){
getMoreData(b, function(c){
getMoreData(c, function(d){
getMoreData(d, function(e){
// Спустились на пятый уровень вложенности
console.log(e); // Только здесь используем полученные данные
});
});
});
});
});
Здесь пять функций получения данных. Каждый новый уровень вложенности добавляет сложности, и для выполнения каждого нового запроса данных нужно ждать завершения предыдущего. Поддерживать и рефакторить такой код сложно. Именно поэтому и появились промисы:
// Новый подход с промисами
getData()
.then(a => getMoreData(a))
.then(b => getMoreData(b))
.then(c => console.log(c))
.catch(console.error);
Но колбэки не ушли полностью — есть случаи, когда они могут быть полезны. Часто их используют для простых одноразовых асинхронных операций или в старом коде или API, где ещё нет поддержки промисов.
События
Другой способ работы с асинхронностью — события (events). В этом случае код не ждёт завершения операции, а реагирует на неё, когда она происходит.
JavaScript использует Event Loop — механизм, который управляет выполнением кода и обработкой событий. Когда происходит событие (например, клик по кнопке), его обработчик попадает в очередь (Event Queue) и выполняется, когда стек вызовов (Call Stack) становится пустым.
Например, у нас есть кнопка:
button.addEventListener("click", () => {
console.log("Кнопка нажата!");
});
Браузер не вызывает обработчик сразу, а ждёт, пока пользователь кликнет по кнопке. Когда это случится, Event Loop добавит обработчик в очередь, и он выполнится, как только освободится главный поток..
Обычно события используют для пользовательских взаимодействий (нажатия кнопок, загрузка файлов) и когда нет строгой последовательности операций — просто реагируем на события.
Вам слово
Приходите к нам в соцсети поделиться своим мнением о статье и почитать, что пишут другие. А ещё там выходит дополнительный контент, которого нет на сайте: шпаргалки, опросы и разная дурка. В общем, вот тележка, вот ВК — велком!