Как использовать resolve и reject внутри callback для управления состоянием Promise в JavaScript
Что проверяют
Понимаете ли вы, как обернуть функцию на колбэках в Promise и что происходит с состоянием после первого вызова resolve или reject. Следом часто спрашивают про util.promisify, про ошибку, брошенную внутри колбэка, и про разницу с async/await.
Как отвечать
Конструктор new Promise(executor) сразу и синхронно вызывает executor(resolve, reject). Задача этой функции: запустить асинхронную операцию и в её колбэке перевести промис в одно из конечных состояний.
function readFileP(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, 'utf8', (err, data) => {
if (err) return reject(err);
resolve(data);
});
});
}Ключевые правила:
- состояние меняется один раз: из
pendingвfulfilledилиrejected. Повторныйresolveилиrejectмолча игнорируется, поэтомуreturn reject(err)нужен не промису, а чтобы не выполнять код дальше; resolveс другим промисом или thenable: он не станет значением, промис «подпишется» на него и примет его итоговое состояние;- синхронный
throwвнутри executor: превращается вreject, а вотthrowвнутри асинхронного колбэка (setTimeout,readFile) промис не отклонит, это необработанное исключение; - обработчики
then: всегда вызываются асинхронно, через очередь микрозадач, даже если промис уже выполнен.
Хорошо упомянуть, что в Node для колбэков вида (err, data) есть util.promisify и готовый модуль fs/promises, а ручная обёртка нужна для событий, таймеров и старых API. Для отмены передают AbortSignal и в обработчике события abort вызывают reject.
Где спотыкаются
Передают в reject строку. Работать будет, но теряется стек, а catch не может проверить instanceof Error. Отклонять стоит объектом Error.
Оборачивают в new Promise то, что уже возвращает промис: fetch или async-функцию. Это антипаттерн: лишний код и риск потерять ошибку, если забыть прокинуть reject.
Считают, что throw в колбэке таймера отклонит промис, и не вызывают reject явно. Промис остаётся в pending навсегда, а await на нём повисает.