OverviewPermalink to “Overview”
Sometimes a component needs to render data that is only available asynchronously. Such data might be fetched from a server, a database, or in general retrieved or computed from an async API.
While Lit's reactive update lifecycle is batched and asynchronous, Lit templates always render synchronously. The data used in a template must be readable at the time of rendering. To render async data in a Lit component, you must wait for the data to be ready, store it so that it's readable, then trigger a new render which can use the data synchronously. Considerations must often be made on what to render while the data is being fetched, or when the data fetch fails as well.
@lit/task package provides a
Task reactive controller to help manage this async data workflow.
Task is a controller that takes an async task function and runs it either manually or automatically when its arguments change. Task stores the result of the task function and updates the host element when the task function completes so the result can be used in rendering.
ExamplePermalink to “Example”
This is an example of using
Task to call an HTTP API via
fetch(). The API is called whenever the
productId parameter changes, and the component renders a loading message when the data is being fetched.
FeaturesPermalink to “Features”
Task takes care of a number of things needed to properly manage async work:
- Gathers task arguments when the host updates
- Runs task functions when arguments change
- Tracks the task status (initial, pending, complete, or error)
- Saves the last completion value or error of the task function
- Triggers a host update when the task changes status
- Handles race conditions, ensuring that only the latest task invocation completes the task
- Renders the correct template for the current task status
- Allows aborting tasks with an
This removes most of the boilerplate for correctly using async data from your code, and ensures robust handling of race conditions and other edge-cases.
What is async data?Permalink to “What is async data?”
Async data is data that's not available immediately, but may be available at some time in the future. For example, instead of a value like a string or an object that's usable synchronously, a promise provides a value in the future.
Async data is usually returned from an async API, which can come in a few forms:
- Promises or async functions, like
- Functions that accept callbacks
- Objects that emit events, such as DOM events
- Libraries like observables and signals
The Task controller deals in promises, so no matter the shape of your async API you can adapt it to promises to use with Task.
What is a task?Permalink to “What is a task?”
At the core of the Task controller is the concept of a "task" itself.
A task is an async operation which does some work to produce data and return it in a Promise. A task can be in a few different states (initial, pending, complete, and error) and can take parameters.
A task is a generic concept and could represent any async operation. They apply best when there is a request/response structure, such as a network fetch, database query, or waiting for a single event in response to some action. They're less applicable to spontaneous or streaming operations like an open-ended stream of events, a streaming database response, etc.
InstallationPermalink to “Installation”
UsagePermalink to “Usage”
Task is a reactive controller, so it can respond to and trigger updates to Lit's reactive update lifecycle.
You'll generally have one Task object for each logical task that your component needs to perform. Install tasks as fields on your class:
As a class field, the task status and value are easily available:
The task functionPermalink to “The task function”
The most critical part of a task declaration is the task function. This is the function that does the actual work.
The task function is given in the
task option. The Task controller will automatically call the task function with arguments, which are supplied with a separate
args callback. Arguments are checked for changes and the task function is only called if the arguments have changed.
The task function takes the task arguments as an array passed as the first parameter, and an options argument as the second parameter:
The task function's args array and the args callback should be the same length.
args functions as arrow functions so that the
this reference points to the host element.
Task statusPermalink to “Task status”
Tasks can be in one of four states:
INITIAL: The task has not been run
PENDING: The task is running and awaiting a new value
COMPLETE: The task completed successfully
ERROR: The task errored
The Task status is available at the
status field of the Task controller, and is represented by the
TaskStatus enum-like object, which has properties
Usually a Task will proceed from
PENDING to one of
ERROR, and then back to
PENDING if the task is re-run. When a task changes status it triggers a host update so the host element can handle the new task status and render if needed.
It's important to understand the status a task can be in, but it's not usually necessary to access it directly.
There are a few members on the Task controller that relate to the state of the task:
status: the status of the task.
value: the current value of the task, if it has completed.
error: the current error of the task, if it has errored.
render(): a method that chooses a callback to run based on the current status.
Rendering TasksPermalink to “Rendering Tasks”
The simplest and most common API to use to render a task is
task.render(), since it chooses the right code to run and provides it the relevant data.
render() takes a config object with an optional callback for each task status:
You can use
task.render() inside a Lit
render() method to render templates based on the task status:
Running tasksPermalink to “Running tasks”
By default, Tasks will run any time the arguments change. This is controlled by the
autoRun option, which defaults to
Auto-runPermalink to “Auto-run”
In auto-run mode, the task will call the
args function when the host has updated, compare the args to the previous args, and invoke the task function if they have changed. A task without
args defined is in manual mode.
Manual modePermalink to “Manual mode”
autoRun is set to false, the task will be in manual mode. In manual mode you can run the task by calling the
.run() method, possibly from an event handler:
In manual mode you can provide new arguments directly to
If arguments are not provided to
run(), they are gathered from the
Aborting tasksPermalink to “Aborting tasks”
A task function can be called while previous task runs are still pending. In these cases the result of the pending task runs will be ignored, and you should try to cancel any outstanding work or network I/O in order to save resources.
You can do with with the
AbortSignal that is passed in the
signal property of the second argument to the task function. When a pending task run is superseded by a new run, the
AbortSignal that was passed to the pending run is aborted to signal the task run to cancel any pending work.
AbortSignal doesn't cancel any work automatically - it is just a signal. To cancel some work you must either do it yourself by checking the signal, or forward the signal to another API that accepts
The easiest way to use the
AbortSignal is to forward it to an API that accepts it, like
Forwarding the signal to
fetch() will cause the browser to cancel the network request if the signal is aborted.
You can also check if a signal has been aborted in your task function. You should check the signal after returning to a task function from an async call.
throwIfAborted() is a convenient way to do this:
Task chainingPermalink to “Task chaining”
Sometimes you want to run one task when another task completes. This can be useful if the tasks have different arguments so that the chained task may run without the first task running again. In this case it'll use the first task like a cache. To do this you can use the value of a task as an argument to another task:
You can also often use one task function and await intermediate results: