Асинхронные операции и события

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


Асинхронный рендеринг компонентов

Marko поддерживает асинхронный рендеринг через промисы и async/await. Это позволяет загружать данные перед рендерингом компонента, не блокируя основной поток.

// example.marko
<async-component data=asyncData/>
// async-component.marko.js
import { component } from "@marko/runtime";

export default component({
    async onCreate(input) {
        this.state.data = await fetchData(input.id);
    }
});

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

  • onCreate может быть асинхронной функцией. Рендеринг компонента будет ждать завершения всех асинхронных операций.
  • Асинхронные данные можно использовать для условного отображения контента в шаблоне с помощью обычной логики Marko:
<div>
    $ if(state.data) {
        <p>${state.data.name}</p>
    } else {
        <p>Загрузка...</p>
    }
</div>
  • Асинхронный рендеринг не блокирует серверный поток Node.js, что критично для масштабируемых приложений.

События в Marko

События в Marko реализуются через механизм on и emit, который позволяет компонентам взаимодействовать без прямой зависимости друг от друга. События могут быть синхронными и асинхронными.

Обработка пользовательских событий

<button on-click("handleClick")>Нажать</button>
export default {
    handleClick(event) {
        console.log("Кнопка нажата", event);
    }
};

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

  • on-click связывает DOM-событие с методом компонента.
  • Обработчики могут быть асинхронными:
async handleClick(event) {
    const data = await fetchData();
    this.state.data = data;
}
  • После обновления состояния Marko автоматически повторно рендерит изменённые части компонента, минимизируя количество DOM-операций.

Кастомные события между компонентами

Marko позволяет создавать события, которые компоненты могут эмитить и слушать.

<child-component on-update="handleUpdate"/>
// child-component.marko.js
this.emit("update", { value: 42 });
// parent component
handleUpdate(event) {
    this.state.value = event.value;
}

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

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

Асинхронные вычисления и эффекты

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

export default {
    onCreate() {
        this.state.result = null;

        this.watch("input", async (newVal) => {
            const response = await fetchData(newVal);
            this.setState({ result: response });
        });
    }
};

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

  • watch отслеживает изменения состояния и выполняет асинхронные операции при изменении данных.
  • Обновления состояния в Marko асинхронны и не блокируют пользовательский интерфейс.

Асинхронные потоки данных с Marko Streams

Marko поддерживает рендеринг через потоки, что особенно полезно при работе с большими наборами данных или серверным рендерингом с постепенной загрузкой контента.

import { renderToNodeStream } from "@marko/stream";

const stream = renderToNodeStream(template, input);
stream.pipe(res);

Особенности потокового рендеринга:

  • Контент отправляется клиенту по мере готовности, уменьшая время до первого байта (TTFB).
  • Можно комбинировать с асинхронными API-запросами для динамического формирования страниц.

Асинхронная обработка ошибок

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

async handleClick() {
    try {
        const data = await fetchData();
        this.state.data = data;
    } catch (err) {
        console.error("Ошибка загрузки данных", err);
        this.state.error = true;
    }
}
  • Ошибки могут быть использованы для отображения состояния ошибки в интерфейсе.
  • Рекомендуется централизованно логировать асинхронные ошибки компонентов.

Интеграция с внешними асинхронными библиотеками

Marko легко интегрируется с любыми промис-базированными библиотеками, например axios для HTTP-запросов или socket.io для веб-сокетов.

import axios from "axios";

export default {
    async onCreate() {
        const response = await axios.get("/api/data");
        this.state.data = response.data;
    }
};
  • Комбинация асинхронного рендеринга и событий позволяет создавать динамические интерфейсы с минимальными усилиями.
  • Использование потоков и событий делает возможным построение масштабируемых реактивных приложений.

Асинхронные операции и событийная модель в Marko создают мощный инструмент для построения сложных веб-приложений. Понимание этих механизмов позволяет эффективно управлять состоянием компонентов, минимизировать задержки рендеринга и создавать интерактивные интерфейсы с динамическим контентом.