Головна / Статті

Перенесення Angular HttpClient до серверного модуля Fetch за допомогою withFetch()

Дізнайтеся, чому HttpClient у Angular переходить від XMLHttpRequest до fetch, як за допомогою fetch() можливий edge SSR та стрімінг, і що це означає для ваших інтерцепторів.

Видавець: REACTAPP.TOP6 хв читання1286 слів

Джерела: Stop Using XMLHttpRequest: Why Angular 22 Shifted to the Native Fetch API

Протягом більшої частини свого існування HttpClient у Angular був простим шаром RxJS, який робив використання XMLHttpRequest — API браузера, що існує ще до майже всього іншого у сучасній веб-платформі. Однак цей вибір все частіше стає перешкодою, коли ваше додаток відображається на сервері, працює у середовищах типу edge runtimes або використовує потокові відповіді. У цьому посібнику розглядається, чому важливий бекенд, заснований на методі fetch, як перейти на нього за допомогою одного параметра provider, а також що змінюється та що залишається незмінним у ваших існуючих інтерцепторах.

Чому XMLHttpRequest став перешкодою

XHR був створений у 1999 році компанією Microsoft для функціонування Outlook Web Access. Його архітектура свідчить про його старість: він є залежним від стану, працює за принципом подій та складний у реалізації, а ширша екосистема JavaScript вже майже десятиліття відходить від нього. Більшість бібліотек та середовищ виконання давно обрали метод fetch(), заснований на обіцянках. Angular зберіг XHR у своїй основі переважно через сильну прихильність до зворотної сумісності.

Цей компроміс виглядав інакше, коли Angular почав використовувати беззонну перевірку змін та серверну обробку відображення. У такому середовищі XHR більше не є просто старим кодом, до якого ніхто не хоче торкатися; він активно обмежує місце та спосіб роботи додатку Angular. Бекенд, заснований на методі fetch, дозволяє фреймворку модернізувати свою мережеву інфраструктуру, належним чином обробляти потокові дані та працювати на платформах типу edge.

На момент написання цього тексту останні версії Angular поступово переходять на fetch як основний механізм для HttpClient. Значення параметрів за замовчуванням та функції, які видаляються, змінюються між основними версіями, тому перевіряйте примітки до версії, якою ви користуєтеся, замість того, щоб припускати, який бекенд є активним.

Середовища виконання Edge не постачають XMLHttpRequest

Хостинг бекенду поступово переходить від повних процесів Node.js у контейнерах Docker до легких ізолятів V8, таких як Cloudflare Workers, Deno та Fastly Compute. Якщо ви хочете детальніше дізнатися про цю модель середовища виконання, перегляньте нашу порівняльну статтю про ізоляти Edge, WebAssembly та середовища виконання Node.js.

Ці платформи побудовані навколо сучасних API, що відповідають веб-стандартам, і XMLHttpRequest до них не належить. Традиційна версія Angular SSR, розгорнута на такій платформі, зазнає невдачі в момент, коли HttpClient здійснює свій перший виклик API під час рендерингу сторінки, оскільки відповідний механізм передачі даних просто відсутній.

fetch(), навпаки, доступний у браузерах, Node.js, Deno та інших середовищах виконання. Як тільки HttpClient починає використовувати його, та сама SSR-додатка стає портабельною: її можна рендерувати в Cloudflare Workers або Deno Deploy та здійснювати виклики API з місць, які географічно знаходяться ближче до користувачів.

Заміна бекенду за допомогою withFetch()

Це зміна конфігурації, а не рефакторинг. Компоненти та сервіси продовжують викликати this.http.get() та подібні функції точно так само, як і раніше; змінюється лише налаштування постачальника у конфігурації вашого додатку.

У наведеному нижче фрагменті HttpClient реєструється через provideHttpClient(), і передаються дві функції. withFetch() вибирає транспорт, заснований на методі fetch, а withInterceptors() реєструє функціональний інтерцептор автентифікації, який описаний далі.

import { ApplicationConfig } from '@angular/core';
import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http';
import { authInterceptor } from './core/auth.interceptor';

export const appConfig: ApplicationConfig = {
  providers: [
    // Injects HttpClient using the modern Fetch API backend
    provideHttpClient(
      withFetch(),
      withInterceptors([authInterceptor])
    )
  ]
};

Внутрішньо withFetch() просить Angular використовувати FetchBackend замість HttpXhrBackend як компонент, який фактично виконує мережевий запит. Усе, що знаходиться вище цього рівня, залишається незмінним.

Одним із обмежень, про які варто знати перед тим, як увімкнути цю функцію, є те, що бекенд методу fetch не повідомляє про події прогресу завантаження, оскільки fetch() не має еквівалента функції прогресу завантаження в XHR. Якщо ваш додаток відображає панель прогресу під час завантаження файлів, обов’язково протестуйте цей процес окремо.

Стрімування відповідей без буферизації всього

XHR був створений для повних, самодостатніх відповідей. Він погано справляється з великими або безперервними даними: завантаження 50-МБ файлу чи обробка даних у фрагментах призводить до накопичення великого буфера в пам’яті, перш ніж JavaScript зможе ним скористатися, що спричиняє напругу на пам’ять та повільну роботу інтерфейсу.

fetch() був розроблений з ReadableStream у своїй основі, тож дані можна обробляти по частинах у міру їх надходження з мережі. Завдяки цьому фреймворку Angular може обробляти надходящі дані в момент їх отримання, замість того щоб чекати на завершення передачі всього об’єму даних.

Це особливо важливо для інтерфейсів у стилі ШІ. Інтерфейс чату, який отримує Server-Sent Events або результати роботи LLM у частинах, потребує того, щоб токени з’являлися в момент їх генерації, і транспортний механізм з підтримкою стрімінгу є основою для цього, не накопичуючи все в пам’яті браузера.

Проте будьте точні щодо того, що ви отримуєте. Звичайний http.get() все одно відправляє кінцеву відповідь після завершення передачі тіла; поступовий доступ здійснюється через події прогресу (reportProgress з параметром observe: 'events'), а для потоків токенів з тривалим існуванням багато команд все ще використовують EventSource або прямий виклик fetch(). Перевірте бажану поведінку в документації до вашої версії Angular.

Що відбувається з вашими інтерцепторами

Щоразу, коли змінюється мережевий двигун, перше запитання у досвідченого інженера стосується мідлверу: чи будуть продовжувати працювати інтерцептори автентифікації та логування?

За наявності функціональних інтерцепторів відповідь — так. Інтерцепція працює з власними абстракціями Angular HttpRequest та HttpResponse, які знаходяться над механізмом передачі даних. Інтерцептори трансформують об’єкт запиту Angular, і лише після завершення всієї ланцюгової обробки кінцевий запит надходить до FetchBackend, який перетворює його на виклик fetch(). Інтерцептор ніколи не торкається первинного запиту.

Наведений нижче приклад є незалежним від конкретного бекенду. Він читає JWT, копіює незмінний запит із заголовком Authorization та передає цю копію до функції next, яка пересилає її наступному інтерцептору, а зрештою — до бекенду.

import { HttpInterceptorFn } from '@angular/common/http';

// This interceptor works perfectly with both XHR and Fetch backends
export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const token = localStorage.getItem('jwt');

  // Clone and mutate the Angular abstraction, not the native request
  const secureReq = req.clone({
    setHeaders: { Authorization: `Bearer ${token}` }
  });
  // Hand off to the next interceptor, and ultimately, the Fetch backend
  return next(secureReq);
};

До цього конкретного фрагмента коду, на відміну від бекенд-частини fetch, застосовуються два застереження. По-перше, localStorage не існує під час серверного рендерингу, тому при SSR або розгортанні на edge-серверах слід інакше захищати виклик або вставляти токен. По-друге, зберігання токенів у localStorage робить їх вразливими до будь-яких загроз типу XSS, а код надсилає Bearer null, коли токен відсутній; код у продакшені має обробляти обидві ситуації.

Інтерцептори на основі класів все ще працюють

Старіші інтерцептори, написані у вигляді класів, які реалізують HttpInterceptor, продовжують працювати з бекендом fetch. Однак вони все більше відстають від решти фреймворку. Поєднання withFetch() з функціональними інтерцепторами, зареєстрованими через withInterceptors(), є підходом, який відповідає сучасним конвенціям Angular, тому варто мігрувати класові версії, коли ви з ними працюєте.

Ключові висновки

  • Перехід HttpClient від XHR до fetch — це тиха, але значуща оновлення: один варіант постачальника, жодних змін у коді виклику.
  • Додайте withFetch() до provideHttpClient(), якщо ваша версія ще не використовує fetch за замовчуванням.
  • SSE в Angular може працювати на Cloudflare Workers, Deno та подібних середовищах виконання на краю мережі, які не мають XMLHttpRequest, саме завдяки fetch.
  • Механізм отримання даних побудований на ReadableStream, що допомагає при роботі з великими об’ємами даних та частковими відповідями, такими як результати роботи LLM, але перевірте, як HttpClient відображає часткові дані перед створенням інтерфейсу для стрімінгу на їх основі.
  • Функціональні інтерсептори (HttpInterceptorFn) продовжують працювати без змін; перевірте прогрес завантаження та будь-які шляхи коду, які працюють лише в SSR, після зміни.