Отмена запросов

В SkateJS управление асинхронными операциями играет важную роль, особенно при работе с сетевыми запросами или таймерами внутри компонентов. Возможность отменять запросы позволяет предотвратить утечки памяти, исключить работу с устаревшими данными и повысить отзывчивость интерфейса.

Управление асинхронными операциями

Компоненты SkateJS основаны на классовой или функциональной модели, где жизненный цикл строго регламентирован методами connectedCallback, disconnectedCallback и реактивными свойствами. Асинхронные операции, такие как fetch, setTimeout или Promise, должны быть безопасно завершены при удалении компонента из DOM.

Простейший подход — хранить ссылки на активные операции и очищать их в disconnectedCallback:

import { define, Component, h } from 'skatejs';

class AsyncExample extends Component {
  timeoutId;

  connectedCallback() {
    super.connectedCallback();
    this.timeoutId = setTimeout(() => {
      console.log('Асинхронная задача выполнена');
    }, 5000);
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    clearTimeout(this.timeoutId);
  }

  render() {
    return <div>Асинхронный пример</div>;
  }
}

define('async-example', AsyncExample);

В этом примере clearTimeout предотвращает выполнение кода после удаления компонента, что обеспечивает корректное управление жизненным циклом.

Отмена HTTP-запросов с AbortController

Для сетевых запросов используется стандартный API AbortController. Этот механизм позволяет отменять fetch при необходимости:

import { define, Component, h } from 'skatejs';

class FetchExample extends Component {
  controller;

  connectedCallback() {
    super.connectedCallback();
    this.controller = new AbortController();

    fetch('https://api.example.com/data', { signal: this.controller.signal })
      .then(response => response.json())
      .then(data => console.log(data))
      .catch(err => {
        if (err.name === 'AbortError') {
          console.log('Запрос был отменён');
        } else {
          console.error(err);
        }
      });
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    this.controller.abort();
  }

  render() {
    return <div>Пример fetch с отменой</div>;
  }
}

define('fetch-example', FetchExample);

Ключевые моменты:

  • Создание AbortController в connectedCallback привязывает его к жизненному циклу компонента.
  • Передача signal в fetch обеспечивает возможность прерывания запроса.
  • В disconnectedCallback вызывается abort(), что автоматически приводит к выбросу исключения AbortError внутри промиса.

Управление несколькими запросами

Если компонент выполняет несколько параллельных запросов, следует использовать коллекцию контроллеров для управления ими:

import { define, Component, h } from 'skatejs';

class MultiFetchExample extends Component {
  controllers = [];

  connectedCallback() {
    super.connectedCallback();

    for (let i = 0; i < 3; i++) {
      const controller = new AbortController();
      this.controllers.push(controller);

      fetch(`https://api.example.com/data/${i}`, { signal: controller.signal })
        .then(res => res.json())
        .then(data => console.log(`Данные ${i}:`, data))
        .catch(err => {
          if (err.name === 'AbortError') console.log(`Запрос ${i} отменён`);
        });
    }
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    this.controllers.forEach(c => c.abort());
    this.controllers = [];
  }

  render() {
    return <div>Несколько fetch-запросов с отменой</div>;
  }
}

define('multi-fetch-example', MultiFetchExample);

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

Интеграция с реактивными свойствами

SkateJS поддерживает реактивные свойства через props. Можно автоматически инициировать запрос при изменении свойства и отменять предыдущий:

import { define, Component, h } from 'skatejs';

class ReactiveFetch extends Component {
  static props = {
    query: { attribute: true }
  };

  controller;

  updated(prevProps) {
    if (prevProps.query !== this.query) {
      if (this.controller) this.controller.abort();
      this.controller = new AbortController();

      fetch(`https://api.example.com/search?q=${this.query}`, { signal: this.controller.signal })
        .then(res => res.json())
        .then(data => console.log(data))
        .catch(err => {
          if (err.name === 'AbortError') console.log('Предыдущий запрос отменён');
        });
    }
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    if (this.controller) this.controller.abort();
  }

  render() {
    return <div>Реактивный fetch: {this.query}</div>;
  }
}

define('reactive-fetch', ReactiveFetch);

Особенности реализации:

  • Метод updated вызывается при изменении свойства query.
  • Предыдущий запрос отменяется перед выполнением нового, предотвращая конфликт устаревших данных.
  • AbortController привязывается к каждому запросу, обеспечивая точную отмену.

Практические рекомендации

  • Всегда очищать асинхронные операции в disconnectedCallback для предотвращения утечек памяти.
  • Использовать AbortController для всех сетевых запросов, особенно при динамическом обновлении данных.
  • Для нескольких параллельных запросов хранить контроллеры в массиве и завершать их циклически.
  • Реактивные свойства удобно сочетать с автоматической отменой предыдущих запросов, что делает компонент предсказуемым и безопасным.

Этот подход к отмене запросов обеспечивает стабильную работу компонентов SkateJS в сложных интерфейсах с динамическим контентом и большим количеством асинхронных операций.