Загружаю комментарии...
\nОтвет сгенерирован нейросетью и может содержать ошибки
Это вопрос о асинхронной обработке на фронтенде и бэкенде. Я расскажу несколько подходов.
Сервер отправляет данные частями, не ждав полного рендеринга:
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio
import json
app = FastAPI()
@app.get("/api/render-stream")
async def render_stream():
async def event_generator():
# Отправляем заголовок страницы
yield '{"type": "header", "content": "<header>Мой сайт</header>"}
await asyncio.sleep(0.5)
# Отправляем основной контент
yield '{"type": "content", "content": "<main>Основной контент</main>"}
await asyncio.sleep(1)
# Отправляем сайдбар
yield '{"type": "sidebar", "content": "<aside>Боковая панель</aside>"}
await asyncio.sleep(0.3)
# Отправляем футер
yield '{"type": "footer", "content": "<footer>Подвал</footer>"}
return StreamingResponse(
event_generator(),
media_type="application/x-ndjson" # новая строка = новый JSON
)
На фронтенде:
const eventSource = new EventSource('/api/render-stream');
function renderPart(type, content) {
const element = document.querySelector(`[data-section="${type}"]`);
if (element) {
element.innerHTML = content;
}
}
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
renderPart(data.type, data.content);
};
Результат: пользователь видит части страницы по мере их готовности, не ждёт полного рендеринга.
Бэкенд отправляет HTML фрагменты, фронтенд вставляет их на лету:
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
app = FastAPI()
@app.get("/page")
async def get_page():
async def generate():
yield "<html><head><title>Моя страница</title></head><body>\n"
# Заголовок
yield "<header>Добро пожаловать</header>\n"
# Медленный контент (например, запрос к БД)
await asyncio.sleep(2)
yield "<main>\n"
yield " <h1>Основной контент</h1>\n"
yield " <p>Контент после 2 секунд ожидания</p>\n"
yield "</main>\n"
# Сайдбар
await asyncio.sleep(1)
yield "<aside>\n"
yield " <h2>Рекомендации</h2>\n"
yield " <ul><li>Товар 1</li></ul>\n"
yield "</aside>\n"
yield "</body></html>"
return StreamingResponse(generate(), media_type="text/html")
В браузере: HTML парсится и отрисовывается по мере получения, не дожидаясь конца потока.
<div id="content">
<h1>Моя страница</h1>
<!-- Этот контент загружается только при скролле -->
<div class="lazy-section" data-section="comments">
<p>Загружаю комментарии...</p>
</div>
</div>
<script>
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const section = entry.target.dataset.section;
// Загружаем контент асинхронно
fetch(`/api/section/${section}`)
.then(res => res.text())
.then(html => {
entry.target.innerHTML = html;
});
observer.unobserve(entry.target);
}
});
});
document.querySelectorAll('.lazy-section').forEach(el => {
observer.observe(el);
});
</script>
<!DOCTYPE html>
<html>
<head>
<style>
.skeleton {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
</style>
</head>
<body>
<!-- Показываем скелет/каркас (fast paint) -->
<div id="header" class="skeleton" style="height: 50px;"></div>
<div id="content" class="skeleton" style="height: 300px;"></div>
<div id="sidebar" class="skeleton" style="height: 200px;"></div>
<script>
// Загружаем реальный контент асинхронно
Promise.all([
fetch('/api/header').then(r => r.text()),
fetch('/api/content').then(r => r.text()),
fetch('/api/sidebar').then(r => r.text())
]).then(([header, content, sidebar]) => {
document.getElementById('header').innerHTML = header;
document.getElementById('content').innerHTML = content;
document.getElementById('sidebar').innerHTML = sidebar;
});
</script>
</body>
</html>
Пользователь видит каркас за 50мс, вместо ожидания 2 секунд.
Тяжёлые вычисления не блокируют рендеринг:
// main.js
const worker = new Worker('worker.js');
// Отправляем задачу в worker
worker.postMessage({ data: largeArray });
// Страница продолжает рендериться
console.log('Страница рендерится');
worker.onmessage = (e) => {
const result = e.data;
console.log('Результат вычисления:', result);
// Обновляем DOM
document.getElementById('result').textContent = result;
};
// worker.js
self.onmessage = (e) => {
const data = e.data;
// Тяжёлые вычисления в отдельном потоке
let result = 0;
for (let i = 0; i < 1000000000; i++) {
result += i;
}
// Отправляем результат обратно
self.postMessage({ result });
};
from fastapi import FastAPI
from fastapi.responses import FileResponse
from fastapi_cache2 import FastAPICache
from fastapi_cache2.backends.redis import RedisBackend
app = FastAPI()
@app.get("/page")
@cached(expire=3600) # Кешируем на 1 час
async def get_page():
# Генерируем страницу один раз
# Всем остальным пользователям отправляем из кеша (мгновенно)
return render_template("page.html")
React 18+ не блокирует рендеринг:
import { Suspense } from 'react';
function Page() {
return (
<>
{/* Заголовок рендерится сразу */}
<Header />
{/* Контент загружается асинхронно */}
<Suspense fallback={<div>Загружаю...</div>}>
<SlowContent />
</Suspense>
{/* Сайдбар не ждёт SlowContent */}
<Sidebar />
</>
);
}
<div hx-get="/api/comments" hx-trigger="revealed">
<p>Загружаю комментарии...</p>
</div>
Когда div становится видимым в viewport — HTMX автоматически загружает контент.
| Метод | Скорость | Сложность | Лучше для |
|---|---|---|---|
| SSE/Streaming | Быстро | Средняя | Большие страницы с долгой генерацией |
| Lazy Loading | Очень быстро | Простая | Бесконечный скролл, галереи |
| Progressive Enhancement | Мгновенно | Простая | UX — каркас показывается сразу |
| Web Workers | Очень быстро | Средняя | CPU-интенсивные операции |
| Кеширование | Мгновенно | Простая | Статический или редко меняющийся контент |
| Concurrent Rendering | Быстро | Средняя | React приложения |
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio
app = FastAPI()
@app.get("/page")
async def get_page():
async def generate_page():
# 1. Отправляем заголовок (fast paint)
yield "<html><body>\n<h1>Мой сайт</h1>\n"
# 2. Загружаем основной контент из БД
content = await fetch_main_content()
yield f"<main>{content}</main>\n"
# 3. Загружаем сайдбар (может быть медленнее)
sidebar = await fetch_sidebar()
yield f"<aside>{sidebar}</aside>\n"
yield "</body></html>"
return StreamingResponse(generate_page(), media_type="text/html")
Не ждать рендеринга можно несколькими способами:
Выбирайте в зависимости от типа контента и требований к UX.