Skip to main content

Promise.withResolvers()

Promise.withResolvers() створює новий проміс і одразу віддає його разом із функціями resolve та reject, якими ним керують ззовні. Це стандартна, безпечна заміна старому трюку з витягуванням resolve і reject з конструктора new Promise.

Теорія

TL;DR

  • Виклик Promise.withResolvers() повертає об'єкт { promise, resolve, reject }.
  • Executor не викликається взагалі: немає колбека (resolve, reject) => {}, є лише готовий об'єкт.
  • Це офіційна форма патерну deferred: промісом керують з будь-якого місця коду після його створення.
  • Замінює старий прийом з двома зовнішніми змінними, який був неочевидним і погано типізувався.
  • Підтримка: Chrome 120+, Node.js 20.11+, Deno 1.41+, Safari 17.4+.

Швидкий приклад

javascript
const { promise, resolve, reject } = Promise.withResolvers(); setTimeout(() => resolve('Успіх'), 1000); promise.then(console.log).catch(console.error);

Через секунду в консолі буде:

javascript
Успіх

Усе те саме, що раніше доводилося робити «вручну» через new Promise.

Що саме повертає метод

Метод створює новий проміс і одразу повертає об'єкт із трьома властивостями:

ВластивістьТипОпис
promisePromiseСам проміс, який можна повернути або дочекатися
resolveFunctionПереводить проміс у стан fulfilled
rejectFunctionПереводить проміс у стан rejected

Тобто це офіційний спосіб отримати зовнішні resolve та reject, без new Promise((resolve, reject) => { ... }).

Сигнатура, як її бачить TypeScript:

javascript
Promise.withResolvers<T>() => { promise: Promise<T>, resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void }

Це робить код безпечнішим: IDE знає тип promise і тип аргументу його resolve.

Як це робили раніше

До появи Promise.withResolvers() писали так:

javascript
let resolve, reject; const promise = new Promise((res, rej) => { resolve = res; reject = rej; });

Виглядало це неочевидно (особливо для TypeScript, де змінні лишалися з типом undefined до присвоєння) і було потенційно небезпечно: resolve і reject можна було випадково перевизначити.

Тепер те саме робиться безпечно й декларативно:

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

Проста аналогія: Promise.withResolvers() це проміс, який одразу йде в комплекті з пультом керування. Раніше кнопки resolve і reject доводилося виколупувати вручну, тепер вони є «з коробки».

Практичні сценарії

Очікування події. Класичний випадок, коли завершенням проміса треба керувати ззовні:

javascript
function waitForEvent(emitter, event) { const { promise, resolve } = Promise.withResolvers(); emitter.once(event, resolve); return promise; }

Тепер можна писати:

javascript
await waitForEvent(button, 'click'); console.log('Кнопку натиснуто');

Відкладені задачі (deferred). Проміс створюють в одному місці, а завершують у зовсім іншому:

javascript
const deferred = Promise.withResolvers(); // десь у коді setTimeout(() => deferred.resolve('Готово'), 2000); // а в іншому місці deferred.promise.then(console.log);

Через 2 секунди в консолі буде Готово.

Комбінація з AbortController. Зовнішній reject зручно чіпляти до сигналу скасування:

javascript
const controller = new AbortController(); const { promise, resolve, reject } = Promise.withResolvers(); controller.signal.addEventListener('abort', () => reject(new Error('Скасовано'))); setTimeout(() => resolve('Успішно'), 2000); // десь пізніше controller.abort(); promise.catch(console.error);

Виведе:

javascript
Error: Скасовано

Особливості поведінки і підтримка

ОсобливістьОпис
Створює новий Promiseбез потреби вручну писати конструктор
Зручно для патерну deferredколи результат стане відомий пізніше
Не викликає executorжодних аргументів (resolve, reject), лише об'єкт
Дозволяє керувати ззовніпромісом можна керувати після його створення

Підсумкова таблиця:

ПунктЗначення
Promise.withResolvers()Створює проміс і повертає resolve та reject
Повертає{ promise, resolve, reject }
ПеревагиЧистіше, безпечніше і типобезпечніше, ніж new Promise(...)
Використовують дляВідкладених задач, подій, зовнішнього керування промісом
ПідтримкаChrome 120+, Node.js 20.11+, Deno 1.41+, Safari 17.4+

Типові помилки

  • Забути про catch на promise. Якщо десь викличуть reject, а обробника немає, отримаєте unhandled rejection. Це особливо легко проґавити, коли reject живе в іншому модулі.
  • Створити проміс і ніколи його не завершити. Якщо жоден resolve чи reject не викликається, await на ньому зависає назавжди, а разом із ним течуть замикання і підписки.
  • Вважати, що повторний resolve щось змінює. Проміс завершується один раз: другий виклик resolve або reject мовчки ігнорується.
  • Використовувати там, де вистачає звичайного new Promise. Якщо вся асинхронна логіка вміщається в executor, зовнішні resolve і reject лише розмазують керування по коду.
  • Розраховувати на метод у старих середовищах. Це відносно свіжа можливість: для старих браузерів або Node.js потрібен поліфіл чи звичний new Promise.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.