Mithril — лёгкий и быстрый фреймворк для создания одностраничных приложений (SPA), который активно использует асинхронные операции через промисы. Встроенные механизмы Mithril позволяют удобно управлять загрузкой данных, обновлением интерфейса и обработкой ошибок без необходимости подключения сторонних библиотек.
m.requestОсновным способом работы с асинхронными данными в Mithril является
функция m.request. Она возвращает объект
Promise, что позволяет использовать привычный синтаксис
.then() и .catch(), а также интегрировать его
с async/await.
m.request({
method: "GET",
url: "/api/users"
})
.then(function(data) {
console.log("Список пользователей:", data);
})
.catch(function(error) {
console.error("Ошибка запроса:", error);
});
Ключевые моменты:
m.request автоматически сериализует запрос и парсит
JSON-ответ.headers.body.m.request({
method: "POST",
url: "/api/users",
body: { name: "Иван", age: 30 },
headers: { "Content-Type": "application/json" }
})
.then(response => console.log("Пользователь создан:", response))
.catch(error => console.error("Ошибка создания:", error));
В Mithril компоненты имеют свойство state, которое может
хранить данные, полученные асинхронно. Использование промисов позволяет
обновлять состояние и автоматически перерисовывать интерфейс после
получения ответа.
const UserList = {
users: [],
oninit: function() {
m.request({ method: "GET", url: "/api/users" })
.then(data => {
UserList.users = data;
})
.catch(err => console.error(err));
},
view: function() {
return m("ul", UserList.users.map(user => m("li", user.name)));
}
};
Особенности:
oninit выполняется при инициализации компонента.async/awaitДля более чистого и читаемого кода можно использовать синтаксис
async/await внутри методов жизненного цикла компонента.
const ProductList = {
products: [],
error: null,
oninit: async function() {
try {
ProductList.products = await m.request({ method: "GET", url: "/api/products" });
} catch(e) {
ProductList.error = e.message;
}
},
view: function() {
if (ProductList.error) return m("div.error", ProductList.error);
return m("ul", ProductList.products.map(p => m("li", p.name)));
}
};
Преимущества использования
async/await:
try/catch.Mithril поддерживает использование Promise.all для выполнения нескольких запросов параллельно. Это полезно, когда нужно загрузить несколько ресурсов до отображения интерфейса.
oninit: async function() {
try {
const [users, posts] = await Promise.all([
m.request({ method: "GET", url: "/api/users" }),
m.request({ method: "GET", url: "/api/posts" })
]);
this.users = users;
this.posts = posts;
} catch(e) {
this.error = e.message;
}
}
Особенности работы с несколькими промисами:
catch.Для UX важно отображать индикаторы загрузки при асинхронных операциях. Mithril не имеет встроенных спиннеров, но можно легко реализовать через состояние компонента.
const DataLoader = {
data: null,
loading: true,
error: null,
oninit: async function() {
try {
DataLoader.data = await m.request({ method: "GET", url: "/api/data" });
} catch(e) {
DataLoader.error = e.message;
} finally {
DataLoader.loading = false;
}
},
view: function() {
if (DataLoader.loading) return m("div", "Загрузка...");
if (DataLoader.error) return m("div.error", DataLoader.error);
return m("pre", JSON.stringify(DataLoader.data, null, 2));
}
};
Принципы:
loading позволяет отслеживать процесс
запроса.finally гарантирует отключение индикатора после
завершения операции.Mithril совместим с любыми стандартными Promise-библиотеками и API,
такими как fetch, axios,
Bluebird. Главное — сохранять реактивность через изменение
состояния компонентов и вызов m.redraw() при
необходимости.
import axios from "axios";
const Posts = {
list: [],
oninit: async function() {
try {
const response = await axios.get("/api/posts");
Posts.list = response.data;
m.redraw();
} catch(e) {
console.error(e);
}
},
view: function() {
return m("ul", Posts.list.map(p => m("li", p.title)));
}
};
m.redraw() после получения данных, если библиотека не
интегрирована с Mithril.Иногда требуется выполнить серию промисов последовательно, где
результат одного зависит от предыдущего. Mithril позволяет делать это
через обычные цепочки промисов или async/await.
async function loadDataSequentially() {
try {
const user = await m.request({ method: "GET", url: "/api/user/1" });
const posts = await m.request({ method: "GET", url: `/api/users/${user.id}/posts` });
return { user, posts };
} catch(e) {
console.error("Ошибка последовательной загрузки:", e);
}
}
Для более сложных сценариев можно комбинировать промисы с Mithril Streams, позволяя создавать реактивные цепочки данных, где промис обновляет поток, автоматически вызывая перерисовку компонента.
const usersStream = m.stream([]);
m.request({ method: "GET", url: "/api/users" })
.then(usersStream);
const UserView = {
view: () => m("ul", usersStream().map(u => m("li", u.name)))
};