{% dj_suspense %} + assign_async

Three tiles, three independent background loaders. Each tile shows its skeleton until its own loader resolves — fastest one in first, no head-of-line blocking.

Dashboard tiles

Watch each tile flip from skeleton → real value at its own pace. Every 3rd reload, "Revenue" fails to demonstrate the .failed branch.

View

from djust import LiveView

class SuspenseDashboardView(LiveView):
    def mount(self, request, **kwargs):
        # All three load concurrently — independent background tasks
        self.assign_async('users',  self._load_users)
        self.assign_async('orders', self._load_orders)
        self.assign_async('revenue', self._load_revenue)

    def _load_users(self):
        time.sleep(0.4)  # external API call
        return {'count': 1234, 'change_pct': 12}

Template

{% dj_suspense await="users" %}
    {% if users.loading %}<div class="skeleton">…</div>{% endif %}
    {% if users.ok %}{{ users.result.count }} users{% endif %}
    {% if users.failed %}<div>Error: {{ users.error }}</div>{% endif %}
{% enddj_suspense %}

See docs.djust.org/api/dj-suspense/ for the full API including fallback="…" template-file alternative + nested suspense boundaries.