Promise.try: полное руководство по асинхронной обработке ошибок в JavaScript (2026)
Руководство · Июнь 2026

Promise.try: полное руководство
Асинхронная обработка ошибок в JavaScript 2026

Забудьте о Promise.resolve().then(fn) и new Promise(resolve => resolve(fn())). Promise.try здесь — и он меняет то, как каждый JavaScript-разработчик обрабатывает функции, которые могут выбросить исключение синхронно или асинхронно. Полное руководство с примерами кода, сценариями использования и стратегиями миграции.

Олег Максимов 1 июня 2026 14 мин чтения

Введение

Если вы когда-нибудь писали код, который должен обрабатывать ошибки от функции, которую вы не контролируете — колбэка, пользовательского обработчика или хука плагина — вы сталкивались с разрывом в обработке синхронных и асинхронных ошибок. Когда функция выбрасывает исключение синхронно, ваш 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 — современный подход
Promise.try(() => riskyFn())
  .then(result => handle(result))
  .catch(err => console.error(err)); // ловит всё, без задержки

// Чисто. Понятно. Без лишнего церемониала.

В этом руководстве я расскажу всё, что нужно знать: что такое Promise.try, как он работает под капотом, реальные сценарии использования, поддержка браузеров, миграция со старых паттернов и полифилл для продакшена.

Что такое Promise.try?

Promise.try — это статический метод конструктора Promise, который принимает функцию в качестве аргумента, вызывает её немедленно и оборачивает результат (будь то простое значение, Promise или выброшенное исключение) в разрешённый или отклонённый Promise.

// Базовое использование Promise.try
// Синхронная функция, возвращающая значение
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 — без микротаски, без задержки.

Проблема: почему старые паттерны неудобны

Паттерн 1: Promise.resolve().then(fn)

Это самый распространённый обходной путь, но у него есть тонкий недостаток: колбэк всегда выполняется на следующей микротаске, даже если функция синхронна.

// Проблема микротаски
console.log("1");

Promise.resolve().then(() => {
  console.log("3"); // выполняется с задержкой на микротаску
});

console.log("2");

// Вывод: 1, 2, 3
// Колбэк был отложен без необходимости

С Promise.try функция вызывается сразу в текущем контексте выполнения, что означает, что синхронные исключения перехватываются немедленно, без задержки, которую добавляет .then().

Паттерн 2: 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 устраняет этот класс ошибок полностью.

Как Promise.try работает под капотом

Спецификация удивительно проста. Вот семантический эквивалент:

// Полифилл 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).

Реальные сценарии использования

1. Цепочки middleware

При построении цепочек middleware, где каждый шаг может быть синхронным или асинхронным, Promise.try предоставляет чистый интерфейс:

// Middleware с синхронными и асинхронными шагами
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));

2. Граница ошибок для пользовательских колбэков

Когда ваша библиотека принимает колбэки от пользователей, вы никогда не знаете, выбросят ли они исключение синхронно или вернут отклонённый 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: "асинхронно плохо!" }

3. Повторные попытки с единообразной обработкой ошибок

// Retry с Promise.try
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);
});

4. Promise.all со смешанными элементами

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)); // Только если ВСЕ упали

5. Обработчики событий

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 против альтернатив

Паттерн Синхр. ошибка? Асинхр. ошибка? Задержка? Читаемость
Promise.try(fn) Ловится Ловится Нет Отличная
Promise.resolve().then(fn) Ловится Ловится Всегда Плохая
new Promise(r => r(fn())) Ловится Ловится Нет Плохая (громоздко, легко ошибиться)
async () => fn() Ловится Ловится Всегда Средняя
try { await fn() } Ловится Ловится Н/Д (в async контексте) Хорошая (но требует async контекста)

Подводные камни

1. Promise.try не принимает thenable напрямую

Promise.try ожидает функцию, а не Promise или thenable. Если у вас уже есть Promise, используйте его напрямую:

// Неправильно — передаётся Promise, а не функция:
Promise.try(existingPromise); // TypeError: fn is not a function

// Правильно — используйте Promise напрямую:
existingPromise.then(result => ...)

// Правильно — оберните в функцию:
Promise.try(() => existingPromise).then(result => ...)

2. .then() всё ещё выполняется на микротаске

Promise.try вызывает fn() синхронно, но любые обработчики .then() всё равно выполняются на следующей микротаске. Вы не делаете всё синхронным — вы просто убираете ненужную задержку на сам вызов функции.

3. Не все движки поддерживают его пока

Хотя 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

Promise.try — часть более широкого движения в JavaScript к более безопасным и выразительным асинхронным примитивам. Наряду с набором возможностей ES2026 (Temporal API, Pattern Matching, Pipeline Operator), он представляет фокус TC39 на улучшение опыта разработчика и корректность кода.

Если вы сравниваете фронтенд-фреймворки, понимание Promise.try особенно ценно при работе с конкурентными возможностями React и паттернами получения данных. Ознакомьтесь с моим сравнением React vs Next.js, чтобы узнать, как современные асинхронные паттерны вписываются в React Server Components и Server Actions.

Нужна надёжная разработка? Мои услуги веб-разработчика

FAQ

Что такое Promise.try в JavaScript?
Promise.try — это новый статический метод конструктора Promise, который оборачивает функцию (синхронную или асинхронную) в Promise, перехватывая как синхронные исключения, так и асинхронные rejections единым обработчиком. В отличие от Promise.resolve().then(fn), он вызывает функцию немедленно без задержки на микротаску. Пример: Promise.try(() => JSON.parse(data)).then(result => handle(result)).catch(err => console.log(err)).
Чем Promise.try отличается от Promise.resolve().then()?
Два ключевых отличия: (1) Promise.resolve().then(fn) всегда добавляет задержку на микротаску — колбэк выполняется на следующем тике даже для синхронных функций. Promise.try вызывает функцию немедленно. (2) Promise.try самодокументируем — он ясно сообщает «попробуй выполнить эту функцию как Promise», тогда как Promise.resolve().then(fn) скрывает намерение.
Поддерживается ли Promise.try в браузерах?
Promise.try — это Stage 4 предложение TC39, вышедшее в Chrome 128+ (май 2026), Node.js 22+ и Deno 2.8+. Safari и Firefox заявили о намерении реализовать его. Для продакшена с поддержкой старых браузеров используйте полифилл: Promise.try = Promise.try || (fn => new Promise(resolve => resolve(fn()))).
Какие проблемы решает Promise.try?
Три основные проблемы: (1) Синхронные исключения внутри Promise.resolve().then() перехватываются, но после ненужной задержки. (2) Паттерн Promise.resolve().then(fn) семантически неясен. (3) Создание утилит, принимающих синхр./асинхр. колбэки, требует громоздкого кода вроде (async () => fn())(). Promise.try решает все три проблемы одним чистым методом.
Можно ли использовать полифилл для Promise.try?
Да. Простой полифилл: if (!Promise.try) { Promise.try = (fn) => new Promise((resolve) => resolve(fn())); }. Более полные версии в core-js: import 'core-js/actual/promise/try'. Полифилл оборачивает вызов функции в новый конструктор Promise, который немедленно её выполняет.
Когда использовать Promise.try в коде?
Используйте Promise.try, когда нужно обработать результат функции как Promise, но неизвестно, синхронная она или асинхронная. Сценарии: цепочки middleware, функции-обёртки (логирование, retry, тайм-ауты), error boundaries, системы событий и библиотеки асинхронных утилит. Если функция заведомо асинхронна и возвращает Promise — вызывайте её напрямую, Promise.try не нужен.
Promise.try — это часть ES2025 или ES2026?
Promise.try достиг Stage 4 и включён в спецификацию ECMAScript 2025. Реализации в движках появились к середине 2026 — Chrome 128 выпустил его в конце мая 2026. Полный обзор всех новых возможностей JavaScript, включая Temporal API, Pattern Matching и другие, смотрите в моём руководстве ES2026.

Начните использовать Promise.try сегодня

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 и не только. Нахожусь в Минске и работаю по всему миру, давайте обсудим ваш проект.

Связаться

Обсудим ваш проект

Расскажите о проекте — я дам экспертную оценку по архитектуре, выбору технологий и предварительную смету. Бесплатно.