{% 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.
@action
dj-form-pending
@server_function
dj-transition
ActivityMixin
WizardMixin
dj_suspense
defer()
DataTableMixin
dj-virtual
dj-viewport
UploadMixin
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.