Плагин eslint-plugin-promise

Плагин eslint-plugin-promise предназначен для статического анализа кода, связанного с использованием промисов в JavaScript. Его основная цель — выявление ошибок управления асинхронностью, некорректных цепочек then/catch, а также анти-паттернов, которые приводят к утечкам ошибок, «зависшим» промисам и непредсказуемому поведению.

Проблемная зона, на которую ориентирован плагин, — это не синтаксис промисов как таковой, а логика их использования: возвраты из then, обработка ошибок, вложенность цепочек и смешивание callback- и promise-подходов.


Установка и подключение

Плагин подключается стандартным способом через npm:

npm install eslint-plugin-promise --save-dev

После установки он добавляется в конфигурацию ESLint:

{
  "plugins": ["promise"],
  "extends": ["plugin:promise/recommended"]
}

Использование plugin:promise/recommended включает набор правил, ориентированных на безопасное использование промисов, без необходимости ручной настройки каждой проверки.

Альтернативный вариант — ручная конфигурация:

{
  "plugins": ["promise"],
  "rules": {
    "promise/catch-or-return": "error",
    "promise/always-return": "error"
  }
}

Базовая модель анализа промисов

Статический анализ в eslint-plugin-promise строится вокруг нескольких ключевых наблюдений:

  • промис должен либо возвращаться, либо завершаться обработкой ошибок;
  • цепочки then должны быть линейными и предсказуемыми;
  • отсутствие catch считается критической проблемой;
  • вложенные промисы часто указывают на архитектурные ошибки;
  • создание новых промисов внутри некорректных контекстов приводит к «висячим» задачам.

Правило catch-or-return

Одно из центральных правил — promise/catch-or-return. Оно контролирует, чтобы каждый промис:

  • либо возвращался из функции;
  • либо имел обработку ошибки через catch.

Типичный анти-паттерн:

function loadData() {
  fetch("/api/data")
    .then(response => response.json());
}

Проблема заключается в том, что ошибка запроса не обрабатывается, и промис «теряется» — невозможно отследить сбой.

Корректные варианты:

function loadData() {
  return fetch("/api/data")
    .then(response => response.json())
    .catch(err => {
      console.error(err);
    });
}

или

function loadData() {
  return fetch("/api/data")
    .then(response => response.json());
}

Правило always-return

promise/always-return контролирует, что внутри цепочек then всегда явно возвращается значение или промис.

Нарушение:

fetch("/api/data")
  .then(response => {
    if (!response.ok) {
      throw new Error("Error");
    }
    response.json();
  });

Проблема — отсутствие return, что приводит к возвращению undefined.

Исправленный вариант:

fetch("/api/data")
  .then(response => {
    if (!response.ok) {
      throw new Error("Error");
    }
    return response.json();
  });

Это правило критично для поддержания корректной цепочки промисов, где каждое звено передаёт результат дальше.


Правило no-nesting

promise/no-nesting запрещает вложенные вызовы промисов, когда внутри одного then создаётся новый then.

Проблемный пример:

fetch("/api/user")
  .then(user => {
    return fetch(`/api/orders/${user.id}`)
      .then(orders => {
        return orders.json();
      });
  });

Проблема — глубокая вложенность, ухудшающая читаемость и усложняющая обработку ошибок.

Рекомендуемый вариант — плоская цепочка:

fetch("/api/user")
  .then(user => fetch(`/api/orders/${user.id}`))
  .then(orders => orders.json());

Правило no-return-wrap

promise/no-return-wrap выявляет лишнюю обёртку возвращаемых значений в Promise.resolve или аналогичных конструкциях.

Антипаттерн:

return Promise.resolve(fetch("/api/data"));

В большинстве случаев это избыточно, так как fetch уже возвращает промис:

return fetch("/api/data");

Плагин ориентирован на упрощение цепочек и устранение лишнего уровня абстракции.


Правило prefer-await-to-then

Некоторые конфигурации включают promise/prefer-await-to-then, стимулирующее переход от цепочек .then() к синтаксису async/await.

Пример нарушения:

function getData() {
  return fetch("/api/data")
    .then(res => res.json())
    .then(data => data);
}

Рекомендуемая форма:

async function getData() {
  const res = await fetch("/api/data");
  const data = await res.json();
  return data;
}

Цель правила — снижение когнитивной сложности асинхронного кода.


Правило no-new-statics

promise/no-new-statics контролирует некорректное использование статических методов промисов в ситуациях, где они не нужны.

Антипаттерн:

new Promise.resolve(value);

Правильно:

Promise.resolve(value);

Правило помогает избегать ошибок, связанных с неправильным использованием API Promise.


Поведение ошибок и обработка исключений

Плагин уделяет особое внимание обработке исключений внутри промисов. Основная проблема заключается в том, что исключения в асинхронных цепочках часто теряются.

Неправильный пример:

fetch("/api/data")
  .then(response => {
    JSON.parse(response.data);
  });

Если JSON.parse выбрасывает ошибку, она может остаться необработанной.

Корректный подход:

fetch("/api/data")
  .then(response => {
    try {
      return JSON.parse(response.data);
    } catch (e) {
      return Promise.reject(e);
    }
  })
  .catch(err => {
    console.error(err);
  });

Конфигурация уровней строгости

Каждое правило может иметь один из уровней:

  • off — отключено;
  • warn — предупреждение;
  • error — ошибка сборки.

Пример строгой конфигурации:

{
  "plugins": ["promise"],
  "rules": {
    "promise/catch-or-return": "error",
    "promise/always-return": "error",
    "promise/no-nesting": "error",
    "promise/no-return-wrap": "warn",
    "promise/prefer-await-to-then": "warn"
  }
}

Такой подход часто используется в production-средах, где критична стабильность асинхронного поведения.


Влияние на архитектуру кода

Использование eslint-plugin-promise приводит к структурным изменениям в кодовой базе:

  • цепочки промисов становятся линейными;
  • уменьшается количество вложенных callback-функций;
  • усиливается дисциплина возвратов значений;
  • повышается предсказуемость обработки ошибок;
  • асинхронный код становится более однородным.

Особенно заметен эффект в больших кодовых базах, где без контроля промисов быстро возникает хаос из «потерянных» ошибок и неполных цепочек.


Интеграция с async/await

Хотя плагин исторически ориентирован на .then() цепочки, он также применяется к async/await. В этом случае он проверяет:

  • корректность возвратов;
  • обработку ошибок через try/catch;
  • отсутствие «зависших» промисов.

Пример некорректного кода:

async function load() {
  fetch("/api/data");
}

Исправление:

async function load() {
  const res = await fetch("/api/data");
  return res;
}

или с обработкой ошибок:

async function load() {
  try {
    const res = await fetch("/api/data");
    return res;
  } catch (e) {
    console.error(e);
    throw e;
  }
}