Старі API (події, таймери, бібліотеки з колбеками) зручно «загорнути» в Promise, щоб використовувати з await. Це називають промісифікацією.
Класичний спосіб - конструктор new Promise:
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function loadImage(src) {
return new Promise((resolve, reject) => {
const image = new Image();
image.onload = () => resolve(image);
image.onerror = () => reject(new Error(`Не вдалося завантажити ${src}`));
image.src = src;
});
}
await sleep(500);
const image = await loadImage('/logo.png');
Правила:
resolveіrejectдіють лише перший раз - повторні виклики ігноруються;- виняток, кинутий синхронно всередині функції-виконавця, автоматично відхиляє Promise;
- не загортайте в
new Promiseте, що вже повертає Promise, - це зайвий шар і місце для втрачених помилок.
Promise.withResolvers() (ES2024) повертає Promise разом з його resolve і reject назовні:
const { promise, resolve, reject } = Promise.withResolvers();
Це зручно, коли виконання відбувається не в одному колбеку, а десь пізніше - наприклад, чекаємо на відповідь у WebSocket за ідентифікатором запиту:
const pending = new Map();
function request(socket, payload) {
const id = crypto.randomUUID();
const { promise, resolve, reject } = Promise.withResolvers();
pending.set(id, { resolve, reject });
socket.send(JSON.stringify({ id, ...payload }));
return promise;
}
socket.onmessage = (event) => {
const { id, result, error } = JSON.parse(event.data);
const handlers = pending.get(id);
pending.delete(id);
error ? handlers?.reject(new Error(error)) : handlers?.resolve(result);
};
Раніше для цього писали той самий шаблон вручну, зберігаючи resolve у зовнішні змінні з конструктора.
У Node.js для функцій зі стилем (error, result) є готовий util.promisify, а більшість модулів мають Promise-версії (node:fs/promises).