Асинхронные директивы

FAST Element предоставляет мощный механизм для построения веб-компонентов с высокой производительностью, и одним из ключевых инструментов для управления асинхронным поведением являются асинхронные директивы. Они позволяют управлять динамическим контентом, который зависит от промисов, наблюдаемых данных или внешних источников, без необходимости вручную следить за состоянием загрузки и повторного рендеринга.


Основы асинхронных директив

Асинхронные директивы в FAST Element — это функции, которые могут возвращать значения, промисы или другую директиву, управляя тем, как именно элемент обновляет DOM при изменении состояния. Основная цель — обеспечить эффективное и плавное обновление содержимого, когда данные становятся доступны.

Ключевые свойства асинхронных директив:

  • Автоматическое управление состояниями: pending, fulfilled, rejected.
  • Избежание лишних перерисовок при изменении промиса.
  • Гибкая интеграция с шаблонами через html и repeat.

Директива async

Директива async является стандартным способом работы с промисами в FAST Element. Она принимает промис и возвращает одно из трёх состояний:

import { html, FASTElement, async } from "@microsoft/fast-element";

class AsyncExample extends FASTElement {
    dataPromise = fetch("/api/data").then(res => res.json());
}

AsyncExample.define();

html`
  <div>
    ${async(this.dataPromise, {
        pending: html`<span>Загрузка...</span>`,
        fulfilled: html`<span>${x => x}</span>`,
        rejected: html`<span>Ошибка загрузки</span>`
    })}
  </div>
`

Разбор работы:

  • pending — шаблон, который отображается до завершения промиса.
  • fulfilled — шаблон для успешного выполнения промиса, получаемое значение доступно через коллбек.
  • rejected — шаблон, который рендерится в случае ошибки.

Асинхронная директива автоматически отслеживает текущее состояние промиса и предотвращает перерисовку предыдущих результатов при смене промиса.


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

В реальных приложениях часто требуется работать с несколькими промисами одновременно. Для этого FAST Element поддерживает композицию директив:

html`
  ${async(this.userPromise, {
      pending: html`<span>Загрузка пользователя...</span>`,
      fulfilled: html`
        <div>
          <p>Имя: ${x => x.name}</p>
          ${async(this.postsPromise, {
              pending: html`<span>Загрузка постов...</span>`,
              fulfilled: html`
                <ul>${x => x.map(post => html`<li>${post.title}</li>`)}</ul>
              `,
              rejected: html`<span>Ошибка при загрузке постов</span>`
          })}
        </div>
      `,
      rejected: html`<span>Ошибка при загрузке пользователя</span>`
  })}
`

Особенности:

  • Вложенные директивы async позволяют создавать цепочки асинхронных зависимостей.
  • Каждая директива локально управляет состоянием своего промиса.
  • Можно комбинировать с repeat для динамических списков, загружаемых из промисов.

Использование с ref и состояниями компонента

Асинхронные директивы хорошо интегрируются с реактивными свойствами FAST Element:

class MyComponent extends FASTElement {
    static properties = {
        userId: { type: Number },
        userData: { type: Object }
    };

    connectedCallback() {
        super.connectedCallback();
        this.loadUser();
    }

    loadUser() {
        this.userData = async(fetch(`/api/users/${this.userId}`).then(r => r.json()));
    }
}

html`
  <div>
    ${async(this.userData, {
        pending: html`<span>Загрузка пользователя...</span>`,
        fulfilled: html`<p>${x => x.name}</p>`,
        rejected: html`<span>Ошибка</span>`
    })}
  </div>
`

Преимущества подхода:

  • Свойства компонента могут напрямую хранить промисы.
  • Директива async автоматически реагирует на смену промиса при изменении свойства.
  • Нет необходимости вручную отслеживать состояние загрузки или ошибки.

Оптимизация производительности

Асинхронные директивы поддерживают мемоизацию промисов, что предотвращает повторные запросы при повторном рендеринге:

html`
  ${async(() => this.cachedPromise, { /* шаблоны */ })}
`
  • async повторно использует уже завершённые промисы, если объект промиса не изменился.
  • Уменьшается количество ненужных запросов и перерисовок DOM.

Обработка ошибок и fallback

Встроенный механизм rejected позволяет централизованно обрабатывать ошибки промисов, избегая необходимости писать try/catch в каждом методе:

${async(this.dataPromise, {
    pending: html`<span>Загрузка...</span>`,
    fulfilled: html`<span>${x => x}</span>`,
    rejected: html`<span style="color:red;">Ошибка: ${x => x.message}</span>`
})}
  • x в блоке rejected содержит объект ошибки.
  • Можно использовать разные шаблоны для разных типов ошибок.

Интеграция с repeat и динамическими списками

Асинхронные директивы отлично работают с repeat для отображения данных после их загрузки:

${async(this.itemsPromise, {
    pending: html`<span>Загрузка списка...</span>`,
    fulfilled: html`
      <ul>
        ${x => x.map(item => html`<li>${item.name}</li>`)}
      </ul>
    `,
    rejected: html`<span>Ошибка загрузки списка</span>`
})}
  • repeat используется только внутри fulfilled для рендеринга окончательного результата.
  • Любые изменения промиса автоматически обновляют список.

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