Асинхронные шаблоны и Promise

Lit — современный фреймворк для создания веб-компонентов, который делает работу с шаблонами и реактивностью проще и эффективнее. Одной из мощных возможностей является поддержка асинхронных данных прямо в шаблонах с использованием Promise и специальных конструкций фреймворка. Это позволяет создавать интерфейсы, которые динамически подгружают данные, управляют состояниями загрузки и ошибок, сохраняя чистоту кода и реактивность компонентов.


Использование async и await в шаблонах

В Lit шаблон определяется через функцию render, которая возвращает результат работы функции html. Когда данные получаются асинхронно, например через fetch, их можно обрабатывать с помощью промисов. Стандартный способ — хранить промис в свойстве компонента и использовать его внутри шаблона.

import { LitElement, html } from 'lit';

class AsyncExample extends LitElement {
  static properties = {
    dataPromise: { type: Object }
  };

  constructor() {
    super();
    this.dataPromise = fetch('https://jsonplaceholder.typicode.com/todos/1')
      .then(response => response.json());
  }

  render() {
    return html`
      ${this.dataPromise.then(data => html`
        <div>Задача: ${data.title}</div>
      `)}
    `;
  }
}

customElements.define('async-example', AsyncExample);

В этом примере dataPromise хранит промис, который при разрешении возвращает данные. Внутри шаблона используется метод then, чтобы получить результат и отрендерить его.


Директива until

Lit предоставляет удобную директиву until, которая позволяет рендерить «заглушку» до того момента, пока промис не разрешится. Это особенно полезно для отображения состояния загрузки.

import { until } from 'lit/directives/until.js';

render() {
  return html`
    ${until(
      this.dataPromise.then(data => html`<div>${data.title}</div>`),
      html`<div>Загрузка...</div>`
    )}
  `;
}

Преимущества until:

  • Отображение индикатора загрузки без дополнительной логики.
  • Чистый и компактный код.
  • Возможность указать несколько промисов или fallback-шаблоны.

Обработка ошибок в асинхронных шаблонах

Работа с асинхронными данными требует обработки возможных ошибок. В Lit это можно делать через цепочку catch промиса или внутри директивы until с fallback-шаблоном.

this.dataPromise = fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then(response => {
    if (!response.ok) throw new Error('Ошибка загрузки');
    return response.json();
  })
  .catch(error => ({ error: error.message }));

render() {
  return html`
    ${until(
      this.dataPromise.then(data => data.error
        ? html`<div>Ошибка: ${data.error}</div>`
        : html`<div>Задача: ${data.title}</div>`),
      html`<div>Загрузка...</div>`
    )}
  `;
}

Это позволяет безопасно обрабатывать любые сбои сетевых запросов и сохранять стабильность интерфейса.


Асинхронные вычисляемые свойства

Иногда необходимо вычислять данные динамически на основе других свойств, возвращая промис. В Lit можно организовать это через геттеры и реактивные свойства.

static properties = {
  userId: { type: Number },
  userData: { type: Object }
};

updated(changedProperties) {
  if (changedProperties.has('userId')) {
    this.userData = this.fetchUserData(this.userId);
  }
}

async fetchUserData(id) {
  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
  return await response.json();
}

render() {
  return html`
    ${until(
      this.userData.then(user => html`<div>Имя: ${user.name}</div>`),
      html`<div>Загрузка данных пользователя...</div>`
    )}
  `;
}

Такой подход позволяет автоматически перерендеривать компонент при изменении ключевого свойства и асинхронно подгружать зависимые данные.


Композиция нескольких промисов

Lit поддерживает объединение нескольких промисов для одновременной загрузки нескольких ресурсов. Это делается через стандартный Promise.all.

this.dataPromise = Promise.all([
  fetch('/api/posts').then(r => r.json()),
  fetch('/api/comments').then(r => r.json())
]);

render() {
  return html`
    ${until(
      this.dataPromise.then(([posts, comments]) => html`
        <div>Посты: ${posts.length}</div>
        <div>Комментарии: ${comments.length}</div>
      `),
      html`<div>Загрузка данных...</div>`
    )}
  `;
}

Использование Promise.all в сочетании с until делает код лаконичным и легко поддерживаемым при работе с несколькими источниками данных одновременно.


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

  • Хранить промисы как свойства компонента, чтобы избежать повторного запроса при каждом рендере.
  • Использовать until для асинхронных данных, чтобы минимизировать мерцание интерфейса.
  • Обрабатывать ошибки промисов, чтобы избежать неконтролируемых исключений.
  • При сложных цепочках промисов можно использовать async функции внутри методов компонента вместо длинных цепочек then.

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