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+.
Швидкий приклад
const { promise, resolve, reject } = Promise.withResolvers();
setTimeout(() => resolve('Успіх'), 1000);
promise.then(console.log).catch(console.error);Через секунду в консолі буде:
УспіхУсе те саме, що раніше доводилося робити «вручну» через new Promise.
Що саме повертає метод
Метод створює новий проміс і одразу повертає об'єкт із трьома властивостями:
| Властивість | Тип | Опис |
|---|---|---|
promise | Promise | Сам проміс, який можна повернути або дочекатися |
resolve | Function | Переводить проміс у стан fulfilled |
reject | Function | Переводить проміс у стан rejected |
Тобто це офіційний спосіб отримати зовнішні resolve та reject, без new Promise((resolve, reject) => { ... }).
Сигнатура, як її бачить TypeScript:
Promise.withResolvers<T>() =>
{ promise: Promise<T>, resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void }Це робить код безпечнішим: IDE знає тип promise і тип аргументу його resolve.
Як це робили раніше
До появи Promise.withResolvers() писали так:
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});Виглядало це неочевидно (особливо для TypeScript, де змінні лишалися з типом undefined до присвоєння) і було потенційно небезпечно: resolve і reject можна було випадково перевизначити.
Тепер те саме робиться безпечно й декларативно:
const { promise, resolve, reject } = Promise.withResolvers();Проста аналогія: Promise.withResolvers() це проміс, який одразу йде в комплекті з пультом керування. Раніше кнопки resolve і reject доводилося виколупувати вручну, тепер вони є «з коробки».
Практичні сценарії
Очікування події. Класичний випадок, коли завершенням проміса треба керувати ззовні:
function waitForEvent(emitter, event) {
const { promise, resolve } = Promise.withResolvers();
emitter.once(event, resolve);
return promise;
}Тепер можна писати:
await waitForEvent(button, 'click');
console.log('Кнопку натиснуто');Відкладені задачі (deferred). Проміс створюють в одному місці, а завершують у зовсім іншому:
const deferred = Promise.withResolvers();
// десь у коді
setTimeout(() => deferred.resolve('Готово'), 2000);
// а в іншому місці
deferred.promise.then(console.log);Через 2 секунди в консолі буде Готово.
Комбінація з AbortController. Зовнішній reject зручно чіпляти до сигналу скасування:
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);Виведе:
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.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.