npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

lit-async

v0.3.1

Published

Async directives and helpers for Lit.

Readme

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>`;
  }
}