New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

lit-async

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

lit-async

Async directives and helpers for Lit.

latest
Source
npmnpm
Version
0.3.1
Version published
Maintainers
1
Created
Source

Lit-Async

npm version npm downloads TypeScript Tree Shakeable

A library of lit-html directives and decorators for handling async operations.

✨ Key Features:

  • Drop promises and async generators directly into templates - No wrapper components needed
  • @sync decorator for reactive properties - Automatically sync async state to properties
  • Works everywhere - Child content, attributes, and properties
  • Share generators across multiple directives - Cached values broadcast to all subscribers
  • Type-safe - Full TypeScript support with automatic type inference

Installation

npm install lit-async

Usage

Common Definitions

The following functions and properties are used in the examples below:

const myPromise = new Promise((resolve) =>
  setTimeout(() => resolve('Hello from a promise!'), 1000)
);

async function fetchData() {
  await new Promise(resolve => setTimeout(resolve, 3000));
  return 'Data loaded!';
}

async function *count() {
  for (let i = 1; ; i++) {
    yield i;
    await new Promise(r => setTimeout(r, 1000));
  }
}

async function *colors() {
  const colors = ['lightyellow', 'lightpink', 'lightgreen', 'lightcyan'];
  let i = 0;
  for (;;) {
    yield colors[i++ % colors.length];
    await new Promise((r) => setTimeout(r, 1000));
  }
}

track

A directive that renders the resolved value of a promise or an async generator.

track<T>(state: Promise<T> | AsyncIterable<T> | T, transform?: (value: T) => unknown): unknown

Ownership Policy: track does not own the async sources it receives. It will not call return() on generators or abort() on promises. When disconnected from the DOM, it simply unsubscribes and ignores future values. You are responsible for managing the lifecycle of your async sources.

Error Handling: If a promise rejects or an async generator throws, track logs the error to the console and renders undefined.

Re-render Behavior: track caches the last value received. When the component re-renders but the generator hasn't yielded new values, track displays the last cached value instead of showing nothing.

Child Content

Render the resolved value of a promise directly into the DOM.

import { html } from 'lit';
import { track } from 'lit-async';

html`${track(myPromise)}`

With Async Generator

track also works with async generators, re-rendering whenever the generator yields a new value.

Important: When using async generators with track, store the generator instance in a property to avoid creating new generators on each render. Creating a new generator on every render will cause resource leaks as old generators continue running.

// ✅ Good: Store generator instance
class MyElement extends LitElement {
  _count = count();

  render() {
    return html`Count: ${track(this._count)}`;
  }
}

// ❌ Bad: Creates new generator each render
render() {
  return html`Count: ${track(count())}`;
}

With Transform Function

Provide a second argument to transform the resolved/yielded value before rendering.

class MyElement extends LitElement {
  _count = count();

  render() {
    return html`Count * 2: ${track(this._count, (value) => value * 2)}`;
  }
}

Attribute

You can bind an async generator to an element's attribute. Lit handles this efficiently.

class MyElement extends LitElement {
  _colors = colors();

  render() {
    return html`
      <div style=${track(this._colors, (color) => `background-color: ${color}`)}>
        This div's background color is set by an async generator.
      </div>
    `;
  }
}

Property

track can be used as a property directive to set an element's property to the resolved/yielded value.

class MyElement extends LitElement {
  _count = count();

  render() {
    return html`<input type="number" .value=${track(this._count)} readonly>`;
  }
}

Shared Generator

Multiple track directives can share the same generator instance. All instances will receive the same values simultaneously.

class MyElement extends LitElement {
  _count = count();

  render() {
    return html`
      <p>First instance: ${track(this._count)}</p>
      <p>Second instance: ${track(this._count)}</p>
      <p>With transform (×10): ${track(this._count, (v) => v * 10)}</p>
    `;
  }
}

All three track() directives will display the same count value at the same time.

How it works: The generator runs once, and each yielded value is cached and broadcast to all track() directives using that generator. When a new track() subscribes to an already-running generator, it immediately receives the last yielded value (if any), ensuring all subscribers stay synchronized.

loading

A helper that shows a fallback value while waiting for async operations to complete.

loading<T>(state: Promise<T> | AsyncIterable<T> | T, loadingValue: unknown, transform?: (value: T) => unknown): AsyncIterable<unknown>
import { html } from 'lit';
import { track, loading } from 'lit-async';

html`${track(loading(fetchData(), 'Fetching data...'))}`

You can also provide a custom template for the loading state:

const loadingTemplate = html`<span>Please wait...</span>`;

html`${track(loading(fetchData(), loadingTemplate))}`

@sync

A decorator that automatically syncs a property with values from a Promise or AsyncIterable.

sync<T>(stateFactory: (this: any) => Promise<T> | AsyncIterable<T> | T): PropertyDecorator

Requirements:

  • Must use the accessor keyword with the property
  • TypeScript must NOT have experimentalDecorators: true (uses standard decorators)

Basic Example:

import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import { sync } from 'lit-async';

@customElement('my-element')
class MyElement extends LitElement {
  // Sync an async generator
  @sync(() => (async function*() {
    for (let i = 0; ; i++) {
      yield i;
      await new Promise(r => setTimeout(r, 1000));
    }
  })())
  accessor count: number | undefined;

  render() {
    return html`<p>Count: ${this.count ?? 'Loading...'}</p>`;
  }
}

Using this context:

@customElement('user-profile')
class UserProfile extends LitElement {
  @property() userId!: string;

  // Factory function can access 'this'
  @sync(function() {
    return fetch(`/api/users/${this.userId}`).then(r => r.json());
  })
  accessor userData: User | undefined;

  render() {
    return html`<p>User: ${this.userData?.name ?? 'Loading...'}</p>`;
  }
}

Keywords

lit

FAQs

Package last updated on 28 Oct 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts