Learn JavaScript

Promise.withResolvers() - создание промиса с внешними resolve и reject в JavaScript

Promise.withResolvers() нужен, когда хочется создать промис и сразу получить связанные с ним функции resolve и reject отдельно. Метод вызывается у Promise, не принимает аргументов и возвращает объект с тремя свойствами: самим промисом и двумя функциями для его завершения.

Это удобная альтернатива шаблону, где resolve и reject приходится сохранять вручную через new Promise(...).

Синтаксис

Базовая запись выглядит так:

Promise.withResolvers()

Метод не принимает обязательных аргументов.

Что возвращает Promise.withResolvers()

Метод возвращает обычный объект такого вида:

{
  promise,
  resolve,
  reject
}

Где:

  • promise - созданный объект Promise
  • resolve - функция для успешного завершения этого промиса
  • reject - функция для отклонения этого промиса

Пример:

const result = Promise.withResolvers();

console.log(result.promise instanceof Promise); // true
console.log(typeof result.resolve); // "function"
console.log(typeof result.reject); // "function"

Простой пример

const { promise, resolve } = Promise.withResolvers();

promise.then((value) => {
  console.log(value); // "Готово"
});

resolve("Готово");

Сначала мы создаём промис, потом отдельно решаем, когда именно его завершить.

resolve() успешно завершает связанный промис

Это основное поведение метода.

const { promise, resolve } = Promise.withResolvers();

promise.then((value) => {
  console.log(value); // "ok"
});

resolve("ok");

Функция resolve() работает так же, как resolve внутри обычного new Promise(...).

reject() завершает промис с ошибкой

Если нужно завершить промис неуспешно, используют reject().

const { promise, reject } = Promise.withResolvers();

promise.catch((error) => {
  console.log(error.message); // "boom"
});

reject(new Error("boom"));

После этого промис переходит в состояние rejected.

Обработчики всё равно выполняются асинхронно

Это важный нюанс.

Даже если вызвать resolve() сразу, then() не выполнится синхронно в ту же строку.

console.log("start");

const { promise, resolve } = Promise.withResolvers();

promise.then((value) => {
  console.log(value); // 42
});

resolve(42);

console.log("end");

Результат будет таким:

start
end
42

То есть Promise.withResolvers() не меняет стандартное асинхронное поведение промисов.

resolve() может принять другой промис

Это тоже работает так же, как у обычного resolve в конструкторе Promise.

Если передать в resolve() другой промис, итоговый промис примет его состояние.

const { promise, resolve } = Promise.withResolvers();

resolve(Promise.resolve("inner"));

promise.then((value) => {
  console.log(value); // "inner"
});

То есть здесь промис не получит внутрь объект Promise, а дождётся его результата.

resolve() может принять thenable

Это ещё один полезный нюанс.

const { promise, resolve } = Promise.withResolvers();

resolve({
  then(onFulfilled) {
    onFulfilled("thenable");
  },
});

promise.then((value) => {
  console.log(value); // "thenable"
});

Такой объект тоже будет обработан как промисоподобное значение.

Промис можно завершить только один раз

Это важно помнить в практическом коде.

Если промис уже завершён, последующие вызовы resolve() или reject() ничего не изменят.

const { promise, resolve, reject } = Promise.withResolvers();

promise.then((value) => {
  console.log(value); // "first"
});

resolve("first");
resolve("second");
reject("third");

Сработает только первый вызов завершения.

Чем Promise.withResolvers() отличается от new Promise(...)

Это главное практическое сравнение.

Без Promise.withResolvers() код часто выглядит так:

let resolveFn;
let rejectFn;

const promise = new Promise((resolve, reject) => {
  resolveFn = resolve;
  rejectFn = reject;
});

С Promise.withResolvers() то же самое можно записать короче и понятнее:

const { promise, resolve, reject } = Promise.withResolvers();

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

Promise.withResolvers() сам по себе не запускает асинхронную операцию

Это важное уточнение.

Метод только создаёт связку из промиса и функций управления. Асинхронная логика появляется не сама по себе, а в вашем коде.

const { promise, resolve } = Promise.withResolvers();

setTimeout(() => {
  resolve("done");
}, 1000);

promise.then((value) => {
  console.log(value); // "done"
});

Здесь асинхронность создаёт setTimeout, а не Promise.withResolvers().

Когда это полезно

Promise.withResolvers() особенно удобен, когда:

  • нужно создать промис заранее, а завершить его позже
  • resolve и reject должны быть доступны вне конструктора
  • вы хотите избежать ручного захвата переменных через new Promise(...)
  • промис связан с внешним событием, таймером, очередью или подпиской

Пример:

function wait(ms) {
  const { promise, resolve } = Promise.withResolvers();

  setTimeout(() => {
    resolve(`Прошло ${ms} мс`);
  }, ms);

  return promise;
}

wait(1000).then((message) => {
  console.log(message); // "Прошло 1000 мс"
});

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

Частые ошибки

Думать, что Promise.withResolvers() возвращает сразу сам промис

const result = Promise.withResolvers();

console.log(result.promise);

Метод возвращает не один Promise, а объект с тремя свойствами: promise, resolve и reject.

Забывать брать promise из результата

const { resolve } = Promise.withResolvers();

resolve("ok");

Так можно вызвать resolve, но без promise вы не сможете нормально подписаться на результат через then() или catch().

Ожидать синхронное выполнение then()

const { promise, resolve } = Promise.withResolvers();

promise.then(() => {
  console.log("done");
});

resolve();
console.log("after");

Сначала выведется after, и только потом done.

Пытаться изменить уже завершённый промис

const { promise, resolve, reject } = Promise.withResolvers();

resolve("first");
reject(new Error("boom"));

promise.then((value) => {
  console.log(value); // "first"
});

После первого завершения остальные вызовы уже ничего не меняют.

Думать, что метод сам делает код асинхронным

const { promise } = Promise.withResolvers();

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

Быстрая памятка

  • Promise.withResolvers() вызывается у Promise
  • метод не принимает обязательных аргументов
  • результатом является объект { promise, resolve, reject }
  • resolve() успешно завершает связанный промис
  • reject() завершает связанный промис с ошибкой
  • обработчики then() и catch() всё равно выполняются асинхронно
  • в resolve() можно передать другой промис или thenable
  • промис можно завершить только один раз
  • метод удобен как замена ручному шаблону с new Promise(...) и внешними переменными

Коротко

Promise.withResolvers() в JavaScript нужен, когда хочется создать промис и сразу получить внешние функции resolve и reject без лишнего шаблонного кода. Главное, что стоит запомнить: метод возвращает объект с promise, resolve и reject, не запускает асинхронность сам по себе, ведёт себя как обычный Promise по правилам resolve и reject, и особенно полезен там, где промис создаётся в одном месте, а завершается позже в другом.