Background sync

Background Sync — механизм, позволяющий выполнять асинхронные операции на фоне, не блокируя основной поток интерфейса, с автоматическим обновлением представлений в момент завершения операции. В контексте Mithril.js это особенно полезно для управления состоянием приложения и работы с удалёнными API, сохраняя реактивность компонентов.

Принципы работы

Mithril использует виртуальный DOM и односторонний поток данных. Любые изменения модели требуют явного вызова m.redraw(), если они происходят вне цикла событий Mithril. Background Sync позволяет:

  • Запустить асинхронную задачу.
  • Обновлять модель при завершении задачи.
  • Автоматически инициировать перерисовку компонента.

Асинхронные операции и m.request

Основным инструментом для работы с удалёнными данными является m.request. Он возвращает Promise, что идеально подходит для Background Sync:

let user = null;

function loadUser() {
    return m.request({
        method: "GET",
        url: "/api/user"
    }).then(result => {
        user = result;
    });
}

Здесь loadUser запускает запрос, а по завершении изменяет модель. Для автоматической перерисовки после изменения состояния можно обернуть её вызовом m.redraw():

function loadUser() {
    return m.request({
        method: "GET",
        url: "/api/user"
    }).then(result => {
        user = result;
        m.redraw();
    });
}

Использование oninit и oncreate для Background Sync

В компонентах Mithril асинхронные задачи часто запускаются в хуках жизненного цикла:

const UserProfile = {
    oninit: vnode => {
        vnode.state.loading = true;
        m.request({ method: "GET", url: "/api/user" })
            .then(result => {
                vnode.state.user = result;
            })
            .finally(() => {
                vnode.state.loading = false;
                m.redraw();
            });
    },
    view: vnode => {
        if (vnode.state.loading) return m("p", "Загрузка...");
        return m("div", [
            m("h2", vnode.state.user.name),
            m("p", vnode.state.user.email)
        ]);
    }
};
  • oninit — момент инициализации компонента, подходящее место для Background Sync.
  • vnode.state — локальное состояние, которое безопасно изменять без глобальных переменных.
  • m.redraw() — инициирует перерисовку интерфейса после завершения асинхронной операции.

Автоматическое обновление с m.stream

Для сложных сценариев Background Sync удобно использовать реактивные потоки m.stream:

const userStream = m.stream();

function fetchUser() {
    m.request({ method: "GET", url: "/api/user" }).then(userStream);
}

const UserProfileStream = {
    oninit: fetchUser,
    view: () => m("div", [
        userStream() ? m("p", userStream().name) : m("p", "Загрузка...")
    ])
};
  • m.stream() — создаёт поток данных.
  • Подписка на поток обеспечивает автоматическую реакцию компонента на изменения значения.
  • Избавляет от необходимости вручную вызывать m.redraw().

Ошибки и обработка исключений

Асинхронные операции могут завершаться с ошибкой. В контексте Background Sync это важно для корректного отображения состояния интерфейса:

const state = { user: null, error: null, loading: false };

function loadUser() {
    state.loading = true;
    m.request({ method: "GET", url: "/api/user" })
        .then(result => state.user = result)
        .catch(err => state.error = err)
        .finally(() => {
            state.loading = false;
            m.redraw();
        });
}
  • catch позволяет отлавливать ошибки запроса.
  • finally гарантирует перерисовку независимо от результата.

Параллельные операции

Background Sync в Mithril поддерживает параллельные асинхронные запросы:

function loadData() {
    state.loading = true;
    Promise.all([
        m.request({ url: "/api/users" }),
        m.request({ url: "/api/posts" })
    ]).then(([users, posts]) => {
        state.users = users;
        state.posts = posts;
    }).finally(() => {
        state.loading = false;
        m.redraw();
    });
}
  • Promise.all объединяет несколько запросов, обеспечивая синхронное обновление состояния после завершения всех операций.
  • Поддержка индикаторов загрузки и обработка ошибок остаётся аналогичной одиночным запросам.

Оптимизация рендеринга

Чтобы избежать лишних перерисовок при Background Sync, можно использовать опцию m.redraw.strategy("diff") или контролировать вызовы m.redraw() вручную. Например:

let timer = setInterval(() => {
    m.request({ url: "/api/updates" }).then(data => {
        state.updates = data;
        m.redraw(); // вызывается только при изменении данных
    });
}, 5000);
  • setInterval имитирует непрерывный Background Sync.
  • Перерисовка инициируется только при фактическом изменении состояния.

Интеграция с локальными хранилищами

Background Sync удобно комбинировать с IndexedDB или localStorage для кэширования:

function loadCachedUser() {
    const cached = localStorage.getItem("user");
    if (cached) state.user = JSON.parse(cached);

    m.request({ url: "/api/user" })
        .then(result => {
            state.user = result;
            localStorage.setItem("user", JSON.stringify(result));
        })
        .finally(() => m.redraw());
}
  • Первичная загрузка из кэша обеспечивает мгновенный отклик интерфейса.
  • Асинхронное обновление данных выполняется на фоне.

Ключевые преимущества Background Sync в Mithril

  • Поддержка реактивного интерфейса без блокировки UI.
  • Простая интеграция с m.request и потоками m.stream.
  • Возможность параллельной загрузки и обработки ошибок.
  • Лёгкая интеграция с локальными хранилищами и кэшированием.

Background Sync в Mithril обеспечивает устойчивую и отзывчивую архитектуру, позволяя строить сложные асинхронные интерфейсы с минимальными усилиями по управлению состоянием и перерисовкой компонентов.