Забудьте о Promise.resolve().then(fn) и new Promise(resolve => resolve(fn())).
Promise.try здесь — и он меняет то, как каждый JavaScript-разработчик обрабатывает функции,
которые могут выбросить исключение синхронно или асинхронно. Полное руководство с примерами
кода, сценариями использования и стратегиями миграции.
Если вы когда-нибудь писали код, который должен обрабатывать ошибки от функции,
которую вы не контролируете — колбэка, пользовательского обработчика или хука
плагина — вы сталкивались с разрывом в обработке синхронных и асинхронных
ошибок. Когда функция выбрасывает исключение синхронно, ваш
try/catch ловит его. Когда она возвращает отклонённый Promise, ваш
.catch() ловит его. Но что, если вы не знаете, какой из двух вариантов?
До сих пор каждый JavaScript-разработчик был вынужден писать неудобные обходные пути:
// Старый способ: обёртка через Promise.resolve().then()
Promise.resolve().then(() => riskyFn())
.then(result => handle(result))
.catch(err => console.error(err)); // ловит и синхронные, и асинхронные ошибки
// Но это добавляет ненужную микротаску! Колбэк выполняется на следующем тике.
// И намерение скрыто — мы разрешаем Promise или оборачиваем функцию?
Promise.try — Stage 4 предложение TC39, которое вышло в Chrome 128+ (конец мая 2026), Node.js 22+ и Deno 2.8+ — решает эту проблему элегантно. Он оборачивает любую функцию — синхронную или асинхронную — в Promise, перехватывая синхронные исключения немедленно (без задержки на микротаску), а также обрабатывая асинхронные отклонения.
Promise.try(() => riskyFn())
.then(result => handle(result))
.catch(err => console.error(err)); // ловит всё, без задержки
// Чисто. Понятно. Без лишнего церемониала.
В этом руководстве я расскажу всё, что нужно знать: что такое Promise.try, как он работает под капотом, реальные сценарии использования, поддержка браузеров, миграция со старых паттернов и полифилл для продакшена.
Promise.try — это статический метод конструктора Promise,
который принимает функцию в качестве аргумента, вызывает её немедленно и оборачивает
результат (будь то простое значение, Promise или выброшенное исключение) в разрешённый
или отклонённый Promise.
// Синхронная функция, возвращающая значение
Promise.try(() => 42)
.then(n => console.log(n)); // 42
// Синхронная функция, выбрасывающая исключение
Promise.try(() => { throw new Error("ошибка"); })
.catch(err => console.error(err.message)); // "ошибка"
// Асинхронная функция (возвращает Promise)
Promise.try(async () => {
const data = await fetch("/api/data");
return data.json();
})
.then(json => console.log(json))
.catch(err => console.error(err));
// Смешанный случай — неизвестно, синхронна fn или асинхронна
function wrap(fn) {
return Promise.try(fn).then(result => ({ success: true, result }))
.catch(err => ({ success: false, error: err }));
}
Ключевое понимание: Promise.try вызывает функцию синхронно, затем оборачивает то, что она возвращает или выбрасывает, в Promise. Если функция выбрасывает исключение, Promise.try перехватывает его немедленно и возвращает отклонённый Promise — без микротаски, без задержки.
Promise.resolve().then(fn)Это самый распространённый обходной путь, но у него есть тонкий недостаток: колбэк всегда выполняется на следующей микротаске, даже если функция синхронна.
console.log("1");
Promise.resolve().then(() => {
console.log("3"); // выполняется с задержкой на микротаску
});
console.log("2");
// Вывод: 1, 2, 3
// Колбэк был отложен без необходимости
С Promise.try функция вызывается сразу в текущем контексте выполнения, что означает,
что синхронные исключения перехватываются немедленно, без задержки, которую добавляет
.then().
new Promise(resolve => resolve(fn()))Ещё один распространённый паттерн, который многословен и подвержен ошибкам:
function oldWrap(fn) {
return new Promise((resolve) => {
try {
resolve(fn());
} catch (e) {
resolve(e); // должно быть reject!
}
});
}
// Легко ошибиться — в примере выше ошибка разрешается (resolve) вместо отклонения (reject).
// Promise.try обрабатывает это правильно по дизайну.
Паттерн new Promise(resolve => resolve(fn())) настолько часто содержит
ошибки, что стал известным анти-паттерном. Разработчики забывают обернуть
fn() в try/catch или используют resolve вместо reject. Promise.try
устраняет этот класс ошибок полностью.
Спецификация удивительно проста. Вот семантический эквивалент:
if (!Promise.try) {
Promise.try = function(fn) {
return new Promise(function(resolve) {
resolve(fn());
});
};
}
Вот и всё. Исполнитель (executor) конструктора Promise выполняется
синхронно, поэтому fn() вызывается немедленно. Если он выбрасывает
исключение, оно перехватывается неявным try/catch конструктора Promise, и Promise
отклоняется. Если возвращается значение или Promise, Promise разрешается с ним
(Promise автоматически «разворачивает» thenable).
При построении цепочек middleware, где каждый шаг может быть синхронным или асинхронным, Promise.try предоставляет чистый интерфейс:
function createPipeline(...steps) {
return function(input) {
return steps.reduce(
(chain, step) => chain.then(result => Promise.try(() => step(result))),
Promise.resolve(input)
);
};
}
const pipeline = createPipeline(
(data) => data.trim(), // синхронный
async (data) => { // асинхронный
const validated = await validate(data);
return validated;
},
(data) => data.length > 0 ? data : throw new Error("Пусто") // может выбросить
);
pipeline(" hello ")
.then(result => console.log(result))
.catch(err => console.error(err));
Когда ваша библиотека принимает колбэки от пользователей, вы никогда не знаете, выбросят ли они исключение синхронно или вернут отклонённый Promise:
function safeInvoke(callback, ...args) {
return Promise.try(() => callback(...args))
.then(result => ({ status: "fulfilled", value: result }))
.catch(err => ({ status: "rejected", reason: err.message }));
}
// Использование — оба варианта работают корректно:
safeInvoke((x) => x * 2, 21)
.then(r => console.log(r)); // { status: "fulfilled", value: 42 }
safeInvoke((x) => { throw new Error("плохо!"); }, 10)
.then(r => console.log(r)); // { status: "rejected", reason: "плохо!" }
safeInvoke(async (x) => { throw new Error("асинхронно плохо!"); }, 10)
.then(r => console.log(r)); // { status: "rejected", reason: "асинхронно плохо!" }
function withRetry(fn, { retries = 3, delay = 1000 } = {}) {
return Promise.try(fn).catch(function attempt(err, attemptNo = 1) {
if (attemptNo >= retries) throw err;
return new Promise(resolve => setTimeout(resolve, delay))
.then(() => Promise.try(fn))
.catch(err => attempt(err, attemptNo + 1));
});
}
// Работает как для синхронных, так и для асинхронных функций:
withRetry(() => fetch("/api/data").then(r => r.json()))
.then(data => console.log(data))
.catch(err => console.error("Все попытки неудачны:", err));
withRetry(() => {
const config = readConfigFile(); // может выбросить синхронно
return process(config);
});
async function safeMap(items, fn) {
const results = await Promise.all(
items.map(item => Promise.try(() => fn(item)))
);
return results;
}
// Даже если fn выбрасывает для некоторых элементов, остальные разрешаются:
safeMap([1, 2, 3], (n) => {
if (n === 2) throw new Error("Двойка — плохое число");
return n * 10;
})
.then(r => console.log(r))
.catch(e => console.error(e)); // Только если ВСЕ упали
class Emitter {
constructor() {
this.handlers = new Map();
}
on(event, handler) {
if (!this.handlers.has(event)) this.handlers.set(event, []);
this.handlers.get(event).push(handler);
}
async emit(event, payload) {
const handlers = this.handlers.get(event) || [];
for (const handler of handlers) {
try {
await Promise.try(() => handler(payload));
} catch (err) {
console.error(`Ошибка обработчика ${event}:`, err);
// Продолжаем со следующим обработчиком вместо падения
}
}
}
}
const emitter = new Emitter();
emitter.on("user:created", (user) => console.log("Синхронно:", user.name));
emitter.on("user:created", async (user) => {
await sendWelcomeEmail(user);
});
emitter.emit("user:created", { name: "Алиса" }); // оба обработчика выполнятся
| Среда | Поддержка | Примечания |
|---|---|---|
| Chrome 128+ | Поддерживается | Вышел в конце мая 2026 |
| Node.js 22+ | Поддерживается | Через обновления V8 |
| Deno 2.8+ | Поддерживается | V8 14.9 включает Promise.try |
| Firefox | В разработке | SpiderMonkey реализует |
| Safari | В разработке | WebKit заявил о намерении |
| Старые браузеры | Нужен полифилл | Используйте core-js или простой полифилл ниже |
// Вариант 1: Простой полифилл
if (!Promise.try) {
Promise.try = function(fn) {
return new Promise(function(resolve) {
resolve(fn());
});
};
}
// Вариант 2: Более надёжный (обрабатывает не-функции, контекст)
if (!Promise.try) {
Promise.try = function(fn, ...args) {
if (typeof fn !== 'function') {
return Promise.resolve(fn);
}
return new Promise(function(resolve) {
resolve(fn(...args));
});
};
}
// Вариант 3: core-js (самый полный)
// import 'core-js/actual/promise/try';
| Паттерн | Синхр. ошибка? | Асинхр. ошибка? | Задержка? | Читаемость |
|---|---|---|---|---|
Promise.try(fn) |
Ловится | Ловится | Нет | Отличная |
Promise.resolve().then(fn) |
Ловится | Ловится | Всегда | Плохая |
new Promise(r => r(fn())) |
Ловится | Ловится | Нет | Плохая (громоздко, легко ошибиться) |
async () => fn() |
Ловится | Ловится | Всегда | Средняя |
try { await fn() } |
Ловится | Ловится | Н/Д (в async контексте) | Хорошая (но требует async контекста) |
Promise.try ожидает функцию, а не Promise или thenable. Если у вас
уже есть Promise, используйте его напрямую:
// Неправильно — передаётся Promise, а не функция:
Promise.try(existingPromise); // TypeError: fn is not a function
// Правильно — используйте Promise напрямую:
existingPromise.then(result => ...)
// Правильно — оберните в функцию:
Promise.try(() => existingPromise).then(result => ...)
Promise.try вызывает fn() синхронно, но любые обработчики
.then() всё равно выполняются на следующей микротаске. Вы не делаете
всё синхронным — вы просто убираете ненужную задержку на сам вызов функции.
Хотя Chrome 128+ и Node.js 22+ имеют нативную поддержку, Safari и Firefox всё ещё в разработке. Всегда включайте полифилл для продакшена, ориентированного на широкую аудиторию. Следите за новейшими возможностями языка JavaScript в моём полном руководстве по ES2026. Полный обзор рантайма Node.js 26, где Promise.try будет стандартом, смотрите в Node.js 26: полное руководство.
Миграция со старых паттернов на Promise.try проста:
// БЫЛО: Promise.resolve().then(() => doWork())
Promise.resolve().then(() => doWork())
.then(result => handle(result))
.catch(err => onError(err));
// СТАЛО: Promise.try(() => doWork())
Promise.try(() => doWork())
.then(result => handle(result))
.catch(err => onError(err));
// БЫЛО: new Promise(resolve => resolve(fetchData()))
new Promise(resolve => resolve(fetchData()))
.then(process)
.catch(handleError);
// СТАЛО: Promise.try(() => fetchData())
Promise.try(() => fetchData())
.then(process)
.catch(handleError);
// БЫЛО: (async () => riskyOp())() — IIFE только ради Promise
(async () => riskyOp())().then(handle).catch(onError);
// СТАЛО: Promise.try(() => riskyOp()).then(handle).catch(onError);
В больших кодовых базах ищите паттерны Promise.resolve().then( и
new Promise(resolve => resolve(. Каждый из них механически заменяется
на Promise.try(() => ...). Семантическая разница (тайминг микротаски)
незначительна для подавляющего большинства случаев.
Promise.try — часть более широкого движения в JavaScript к более безопасным и выразительным асинхронным примитивам. Наряду с набором возможностей ES2026 (Temporal API, Pattern Matching, Pipeline Operator), он представляет фокус TC39 на улучшение опыта разработчика и корректность кода.
Если вы сравниваете фронтенд-фреймворки, понимание Promise.try особенно ценно при работе с конкурентными возможностями React и паттернами получения данных. Ознакомьтесь с моим сравнением React vs Next.js, чтобы узнать, как современные асинхронные паттерны вписываются в React Server Components и Server Actions.
Нужна надёжная разработка? Мои услуги веб-разработчика
Promise.try(() => JSON.parse(data)).then(result => handle(result)).catch(err => console.log(err)).Promise.try = Promise.try || (fn => new Promise(resolve => resolve(fn()))).(async () => fn())(). Promise.try решает все три проблемы одним чистым методом.if (!Promise.try) { Promise.try = (fn) => new Promise((resolve) => resolve(fn())); }. Более полные версии в core-js: import 'core-js/actual/promise/try'. Полифилл оборачивает вызов функции в новый конструктор Promise, который немедленно её выполняет.
Promise.try — одно из тех редких дополнений к JavaScript, после которого сразу
задаёшься вопросом, как вы жили без него. Он не добавляет новых возможностей,
которые были недоступны раньше. Но он делает ваш код чище, безопаснее и
понятнее. Каждый раз, когда вы пишете Promise.resolve().then(...)
или оборачиваете функцию в async () => fn(), используйте Promise.try.
Готовы внедрить Promise.try в свои проекты? Добавьте полифилл сегодня, начните использовать его нативно в Node.js 22+ или Deno 2.8+ и приведите в порядок старые обёртки Promise.
Если вы планируете проект по веб-разработке и ищете опытного full-stack разработчика, который следит за новейшими возможностями JavaScript, свяжитесь со мной. Я создаю современные, поддерживаемые приложения, используя лучшие инструменты 2026 года.
Я — full-stack разработчик с 20-летним опытом создания проектов на React, Vue.js, Angular, Node.js и не только. Нахожусь в Минске и работаю по всему миру, давайте обсудим ваш проект.
Расскажите о проекте — я дам экспертную оценку по архитектуре, выбору технологий и предварительную смету. Бесплатно.