← Все статьи

API и хелперы

Подменяй ответы API, перехватывая fetch и XHR

Бэкенд не готов. Или готов, но ты не можешь по команде заставить его вернуть пустой список, ошибку 500 и трёхсекундную задержку. Эта статья показывает, как перехватить fetch и XMLHttpRequest прямо в браузере JS-правилом JustZix, возвращая поддельный JSON, — чтобы ты мог собирать и тестировать фронтенд под любой сценарий.

Зачем вообще перехватывать в браузере

Обычный ответ на «мне нужны фейковые данные» — мок-сервер: MSW, json-server, заглушка в dev-окружении. Это отличные вещи, и для целой команды это правильный выбор. Но иногда хочется чего-то полегче:

JS-правило JustZix живёт в браузере, применяется по URL-паттерну и не требует никаких изменений в проекте. В этом его ниша: быстрый, точечный, легко выбрасываемый моккинг.

Оборачиваем window.fetch

Ключевой трюк — заменить window.fetch своей функцией, которая для каждого запроса решает: подменить его или пропустить. Запусти это на document_start, чтобы установка произошла до того, как код приложения вызовет fetch:

// Сохраняем оригинал, чтобы при необходимости уйти в реальную сеть
const realFetch = window.fetch.bind(window);

// Твоя таблица моков: совпадение по подстроке URL -> фабрика ответа
const mocks = [
  {
    match: '/api/user/profile',
    response: () => jsonResponse({ id: 1, name: 'Ada Lovelace', plan: 'pro' })
  },
  {
    match: '/api/orders',
    response: () => jsonResponse([])   // краевой случай с пустым списком
  }
];

window.fetch = async (input, init) => {
  const url = typeof input === 'string' ? input : input.url;
  const hit = mocks.find(m => url.includes(m.match));
  if (hit) {
    console.log('[mock] intercepted', url);
    return hit.response();
  }
  return realFetch(input, init);   // не замокано -> реальный запрос
};

Сборка синтетического Response

Твоё приложение ждёт настоящий объект Response — оно вызовет .json(), проверит .ok, прочитает .status. Конструктор Response даёт всё это бесплатно:

// Хелпер: ответ 200 OK с JSON
function jsonResponse(data, status = 200) {
  return new Response(JSON.stringify(data), {
    status,
    headers: { 'Content-Type': 'application/json' }
  });
}

Поскольку это настоящий Response, код вида const r = await fetch(...); if (r.ok) return r.json(); работает без изменений. В этом весь смысл — приложение не отличит подделку.

Имитация задержки

Пустые списки и счастливые пути просты. Баги живут на медленном пути: спиннеры, которые никогда не пропадают, гонки, двойные отправки. Добавь задержку перед резолвом:

// Небольшой хелпер для паузы
const sleep = (ms) => new Promise(r => setTimeout(r, ms));

// В таблице моков дожидаемся его перед возвратом
{
  match: '/api/search',
  response: async () => {
    await sleep(3000);            // 3 секунды «загрузки...»
    return jsonResponse({ results: [] });
  }
}

Имитация кодов ошибок

Теперь несчастливые пути. 500, 404, 401, который должен выкинуть тебя на логин, — верни их с правильным статусом, чтобы твоя обработка ошибок действительно сработала:

// 500 Internal Server Error
{
  match: '/api/checkout',
  response: () => jsonResponse(
    { error: 'payment_gateway_timeout' },
    500
  )
}

// Сетевой сбой (fetch отклоняется, а не резолвится)
{
  match: '/api/flaky',
  response: () => Promise.reject(new TypeError('Failed to fetch'))
}

Заметь разницу: 500 всё равно резолвится с r.ok === false, а оборванное соединение отклоняется. Тестируй оба — приложения часто обрабатывают одно и падают на другом.

Оборачиваем XMLHttpRequest

Много старого кода и библиотек вроде axios под капотом всё ещё используют XMLHttpRequest. Перехват fetch их не затрагивает. XHR подделывать неуклюже, но тонкая обёртка покрывает типичный случай:

const RealXHR = window.XMLHttpRequest;

window.XMLHttpRequest = function () {
  const xhr = new RealXHR();
  const realOpen = xhr.open;
  let mockedUrl = null;

  xhr.open = function (method, url, ...rest) {
    if (url.includes('/api/legacy')) mockedUrl = url;
    return realOpen.call(this, method, url, ...rest);
  };

  const realSend = xhr.send;
  xhr.send = function (...args) {
    if (!mockedUrl) return realSend.apply(this, args);

    // Подделываем успешный ответ без обращения к сети
    setTimeout(() => {
      Object.defineProperty(xhr, 'readyState', { value: 4 });
      Object.defineProperty(xhr, 'status', { value: 200 });
      Object.defineProperty(xhr, 'responseText', {
        value: JSON.stringify({ legacy: true })
      });
      xhr.onreadystatechange && xhr.onreadystatechange();
      xhr.onload && xhr.onload();
    }, 0);
  };

  return xhr;
};

Это намеренно минимально — оно покрывает onload / onreadystatechange с JSON-текстом, а это то, что нужно 90% XHR-кода. Если библиотека проверяет getAllResponseHeaders(), тебе придётся заглушить и это. На этом этапе, честно говоря, бери настоящий мок-сервер.

Когда вместо этого взять настоящий мок-сервер

Перехват в браузере — правильный инструмент для быстрой, ограниченной по области, выбрасываемой работы. Переключайся на MSW или json-server, когда:

Правило большого пальца: если мок надо коммитить — используй сервер. Если он должен исчезать при выключении правила — используй JustZix.

Смотри также

Хватит ждать бэкенд. Установи JustZix, помести обёртку fetch в ограниченное JS-правило и собирай фронтенд под каждый сценарий уже сегодня.

Оцени эту статью

Оценок пока нет — оцени первым.

Попробуй сам

Установи JustZix и вставь любой сниппет из этой статьи. Две минуты от нуля до работающего правила на всех твоих устройствах.

Получить JustZix

Возможности · Как это работает · Примеры · Применение