Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sour-ends-occur.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/pacer-lite': minor
---

feat: add pacer-lite library
49 changes: 44 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,51 @@

# TanStack Pacer

A lightweight timing and scheduling library for debouncing, throttling, rate limiting, and managing complex async workflows.
A lightweight timing and scheduling library for debouncing, throttling, rate limiting, queuing, and batching.

- Debouncing, throttling & rate limiting with sync/async support
- Queuing & batching utilities with pause, resume & cancel controls
- Framework adapters (React, Solid, etc.) with convenient hooks
- Fully type‑safe with small, tree‑shakeable utilities
> [!NOTE]
> TanStack Pacer is currently mostly a client-side only library, but it is being designed to be able to potentially be used on the server-side as well.

- **Debouncing**
- Delay execution until after a period of inactivity for when you only care about the last execution in a sequence.
- Synchronous or Asynchronous Debounce utilities with promise support and error handling
- Control of leading, trailing, and enabled options
- **Throttling**
- Smoothly limit the rate at which a function can fire
- Synchronous or Asynchronous Throttle utilities with promise support and error handling
- Control of leading, trailing, and enabled options.
- **Rate Limiting**
- Limit the rate at which a function can fire over a period of time
- Synchronous or Asynchronous Rate Limiting utilities with promise support and error handling
- Fixed or Sliding Window variations of Rate Limiting
- **Queuing**
- Queue functions to be executed in a specific order
- Choose from FIFO, LIFO, and Priority queue implementations
- Control processing speed with configurable wait times or concurrency limits
- Manage queue execution with start/stop capabilities
- Expire items from the queue after a configurable duration
- **Batching**
- Chunk up multiple operations into larger batches to reduce total back-and-forth operations
- Batch by time period, batch size, whichever comes first, or a custom condition to trigger batch executions
- **Async or Sync Variations**
- Choose between synchronous and asynchronous versions of each utility
- Optional error, success, and settled handling for async variations
- Retry and Abort support for async variations
- **State Management**
- Uses TanStack Store under the hood for state management with fine-grained reactivity
- Easily integrate with your own state management library of choice
- Persist state to local or session storage for some utilities like rate limiting and queuing
- **Convenient Hooks**
- Reduce boilerplate code with pre-built hooks like `useDebouncedCallback`, `useThrottledValue`, and `useQueuedState`, and more.
- Multiple layers of abstraction to choose from depending on your use case.
- Works with each framework's default state management solutions, or with whatever custom state management library that you prefer.
- **Type Safety**
- Full type safety with TypeScript that makes sure that your functions will always be called with the correct arguments
- Generics for flexible and reusable utilities
- **Framework Adapters**
- React, Solid, and more
- **Tree Shaking**
- We, of course, get tree-shaking right for your applications by default, but we also provide extra deep imports for each utility, making it easier to embed these utilities into your libraries without increasing the bundle-phobia reports of your library.

### <a href="https://tanstack.com/pacer">Read the docs →</b></a>

Expand Down
91 changes: 70 additions & 21 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,41 +51,45 @@
"label": "Guides",
"children": [
{
"label": "Debouncing Guide",
"to": "guides/debouncing"
"label": "Which Utility Should I Choose?",
"to": "guides/which-pacer-utility-should-i-choose"
},
{
"label": "Async Debouncing Guide",
"to": "guides/async-debouncing"
"label": "Debouncing Guide",
"to": "guides/debouncing"
},
{
"label": "Throttling Guide",
"to": "guides/throttling"
},
{
"label": "Async Throttling Guide",
"to": "guides/async-throttling"
},
{
"label": "Rate Limiting Guide",
"to": "guides/rate-limiting"
},
{
"label": "Async Rate Limiting Guide",
"to": "guides/async-rate-limiting"
},
{
"label": "Queuing Guide",
"to": "guides/queuing"
},
{
"label": "Async Queuing Guide",
"to": "guides/async-queuing"
},
{
"label": "Batching Guide",
"to": "guides/batching"
},
{
"label": "Async Debouncing Guide",
"to": "guides/async-debouncing"
},
{
"label": "Async Throttling Guide",
"to": "guides/async-throttling"
},
{
"label": "Async Rate Limiting Guide",
"to": "guides/async-rate-limiting"
},
{
"label": "Async Queuing Guide",
"to": "guides/async-queuing"
},
{
"label": "Async Batching Guide",
"to": "guides/async-batching"
Expand Down Expand Up @@ -634,7 +638,16 @@
},
{
"label": "Debouncer Examples",
"children": [],
"children": [
{
"label": "liteDebounce",
"to": "examples/vanilla/liteDebounce"
},
{
"label": "LiteDebouncer",
"to": "examples/vanilla/LiteDebouncer"
}
],
"frameworks": [
{
"label": "react",
Expand Down Expand Up @@ -706,7 +719,16 @@
},
{
"label": "Throttler Examples",
"children": [],
"children": [
{
"label": "liteThrottle",
"to": "examples/vanilla/liteThrottle"
},
{
"label": "LiteThrottler",
"to": "examples/vanilla/LiteThrottler"
}
],
"frameworks": [
{
"label": "react",
Expand Down Expand Up @@ -774,7 +796,16 @@
},
{
"label": "Rate Limiter Examples",
"children": [],
"children": [
{
"label": "liteRateLimit",
"to": "examples/vanilla/liteRateLimit"
},
{
"label": "LiteRateLimiter",
"to": "examples/vanilla/LiteRateLimiter"
}
],
"frameworks": [
{
"label": "react",
Expand Down Expand Up @@ -850,7 +881,16 @@
},
{
"label": "Queue Examples",
"children": [],
"children": [
{
"label": "liteQueue",
"to": "examples/vanilla/liteQueue"
},
{
"label": "LiteQueuer",
"to": "examples/vanilla/LiteQueuer"
}
],
"frameworks": [
{
"label": "react",
Expand Down Expand Up @@ -906,7 +946,16 @@
},
{
"label": "Batcher Examples",
"children": [],
"children": [
{
"label": "liteBatch",
"to": "examples/vanilla/liteBatch"
},
{
"label": "LiteBatcher",
"to": "examples/vanilla/LiteBatcher"
}
],
"frameworks": [
{
"label": "react",
Expand Down
110 changes: 110 additions & 0 deletions docs/guides/which-pacer-utility-should-i-choose.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
---
title: Which Pacer Utility Should I Choose?
id: which-pacer-utility-should-i-choose
---

TanStack Pacer provides 5 core utilities for controlling function execution frequency. Here is a one-sentence summary of each utility:

- [**Debouncer**](../debouncing.md) - Executes a function after a period of inactivity. (Rejects other calls during activity)
- [**Throttler**](../throttling.md) - Executes a function at regular intervals. (Rejects all but one call during each interval)
- [**Rate Limiter**](../rate-limiting.md) - Prevents a function from being called too frequently. (Rejects calls when the limit is reached)
- [**Queuer**](../queuing.md) - Processes all calls to a function in order. (Only rejects calls if the queue is full)
- [**Batcher**](../batching.md) - Groups multiple function calls into a single batch. (No rejections)

After choosing which strategy fits your needs, there are additional variations and decisions to consider. This guide provides quick clarifications on the most common decisions you'll need to make.

## Synchronous vs Asynchronous

You may see both a [Debouncer](../debouncing.md) and an [Async Debouncer](../async-debouncing.md) when first exploring TanStack Pacer. Which one should you use?

Each utility comes in both synchronous and asynchronous versions. For most use cases, the simpler synchronous version is sufficient. However, if you need these utilities to handle async logic for you, the complexity of each utility increases significantly. The bundle size of the asynchronous versions of each utility is often more than double the size of the synchronous versions. If you actually need and use some of these additional APIs, the extra complexity is worth it, but don't choose the asynchronous version of a utility unless you actually end up using these features.

> [!TIP] We recommend using the simpler synchronous version of each utility for most use cases. (Debouncer, Throttler, Rate Limiter, Queuer, Batcher)

Luckily, switching between the synchronous and asynchronous versions of a utility is straightforward. For the most part, just replace the import whenever you decide you need to switch.

### When to Use the Asynchronous Version

Use the asynchronous version when you need any of these capabilities:

- **Await Return Values**: Await and use the return value from your function, rather than just calling it for side effects. The synchronous version returns void, while the async version returns a Promise that resolves with your function's result. You can also await the return value to determine when to send another execution when execution order matters.

- **Error Handling**: Built-in error handling with configurable error callbacks, control over whether errors are thrown or swallowed, and error statistics tracking.

- **Extra Callbacks**: Instead of just an `onExecute` callback that comes with the synchronous version, the asynchronous version comes with additional callbacks such as `onSuccess`, `onError`, `onSettled`, and `onAbort`.

- **Concurrency**: For queuing specifically, concurrency support allowing multiple items to be processed simultaneously while maintaining control over how many run at once.

- **Retries and Aborts**: Built-in integration with `AsyncRetryer` for automatic retries of failed executions with configurable backoff strategies, jitter, and retry limits. Cancel in-flight operations using AbortController.

## Pacer Lite vs Pacer

Pacer Lite (`@tanstack/pacer-lite`) is a stripped-down version of the core TanStack Pacer library. It is designed to be used in libraries and npm packages that need minimal overhead and no reactivity features. The Lite version of each utility has the same core functionality as its core counterpart, but with a smaller API surface and a smaller bundle size. Pacer Lite lacks reactivity features, framework adapters, devtools support, and some of the advanced options that the core utilities have.

If you are building an application, use the normal `@tanstack/pacer` package (or your framework adapter like `@tanstack/react-pacer` for React, `@tanstack/solid-pacer` for Solid, etc.). Only use Pacer Lite if you are building a library or npm package that needs to be as lightweight as possible and doesn't need the extra features of the core utilities.

## Which Hook Variation Should I Use?

We will use the Debouncer utility as the main example, but the same principles apply to all the other utilities.

If you are using a framework adapter like React, you will see that there are lots of examples with multiple hook variations. For example, for debouncing you will see:

- [`useDebouncer`](../../framework/react/examples/useDebouncer)
- [`useDebouncedCallback`](../../framework/react/examples/useDebouncedCallback)
- [`useDebouncedState`](../../framework/react/examples/useDebouncedState)
- [`useDebouncedValue`](../../framework/react/examples/useDebouncedValue)

You will also probably see that you can use the core `Debouncer` class directly or the core `debounce` function directly without using a hook.

These are all variations of the same basic debouncing functionality. So, which one should you use?

The answer is: It Depends! 🤷‍♂️

But also: It doesn't really matter too much. They all do essentially the same thing. It's mostly a matter of personal preference and how you want to interact with the utility. Under the hood, a `Debouncer` instance is created no matter what you choose.

You can start with the [`useDebouncer`](../../framework/react/examples/useDebouncer) hook if you don't know which one to use. All of the others wrap the `useDebouncer` hook with different argument and return value signatures.

```tsx
import { useDebouncer } from '@tanstack/react-pacer'
//...
const debouncer = useDebouncer(fn, options)

debouncer.maybeExecute(args) // execute the debounced function
//...
debouncer.cancel() // use Debouncer APIs with full access to the debouncer instance
debouncer.flush()
```

If you only need to create a debounced function and don't need access to the debouncer instance to call methods or use its extra features, use the [`useDebouncedCallback`](../../framework/react/examples/useDebouncedCallback) hook. The `*Callback` versions of the hooks are actually most similar to calling the core functions directly (like `debounce`) but with the memoization setup taken care of for you.

```tsx
import { useDebouncedCallback } from '@tanstack/react-pacer'
//...
const debouncedFn = useDebouncedCallback(fn, options)

debouncedFn(args) // execute the debounced function
//...
```

The other variations are convenience hooks that wrap the `useDebouncer` hook with different argument and return value signatures. For example, the [`useDebouncedState`](../../framework/react/examples/useDebouncedState) hook is useful when you need to debounce a state value.

```tsx
import { useDebouncedState } from '@tanstack/react-pacer'
//...
const [debouncedValue, setDebouncedValue] = useDebouncedState(value, options)

setDebouncedValue(newValue) // set the debounced value (will be debounced state setter)
//...
```

The [`useDebouncedValue`](../../framework/react/examples/useDebouncedValue) hook is useful when your debounced value is derived from an instant value that changes frequently.

```tsx
import { useDebouncedValue } from '@tanstack/react-pacer'
//...
const [instantValue, setInstantValue] = useState(0)
const [debouncedValue] = useDebouncedValue(instantValue, options)
//...
setInstantValue(newValue) // Set the instant value; the debounced value will update automatically, delayed by the wait time
//...
```
7 changes: 6 additions & 1 deletion docs/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ id: overview
TanStack Pacer is a library focused on providing high-quality utilities for controlling function execution timing in your applications. While similar utilities exist elsewhere, we aim to get all the important details right - including ***type-safety***, ***tree-shaking***, and a consistent and ***intuitive API***. By focusing on these fundamentals and making them available in a ***framework agnostic*** way, we hope to make these utilities and patterns more commonplace in your applications. Proper execution control is often an afterthought in application development, leading to performance issues, race conditions, and poor user experiences that could have been prevented. TanStack Pacer helps you implement these critical patterns correctly from the start!

> [!IMPORTANT]
> TanStack Pacer is currently in **alpha** and its API is subject to change.
> TanStack Pacer is currently in **beta** and its API is still subject to change.
>
> The scope of this library may grow, but we hope to keep the bundle size of each individual utility lean and focused.

Expand Down Expand Up @@ -43,6 +43,7 @@ Many of the ideas (and code) for TanStack Pacer are not new. In fact, many of th
- **Async or Sync Variations**
- Choose between synchronous and asynchronous versions of each utility
- Optional error, success, and settled handling for async variations
- Retry and Abort support for async variations
- **State Management**
- Uses TanStack Store under the hood for state management with fine-grained reactivity
- Easily integrate with your own state management library of choice
Expand All @@ -66,3 +67,7 @@ Each utility is designed to be used in a specific way, and each utility has its
See how each utility behaves with this interactive comparison. Move the range slider to observe the differences between debouncing, throttling, rate limiting, queuing, and batching:

<iframe src="https://stackblitz.com/github/TanStack/pacer/tree/main/examples/react/util-comparison?embed=1&view=preview&hideNavigation=1" width="100%" height="1200px" style="border: 1px solid #ccc; border-radius: 4px;"></iframe>

## Pacer Lite

Pacer Lite (`@tanstack/pacer-lite`) is a stripped down version of the core TanStack Pacer library. It is designed to be used in libraries and npm packages that need minimal overhead, and no reactivity features. The Lite version of each utility has the same core functionality of its core counterpart, but is stripped down to have a slightly smaller API surface and a smaller bundle size. Pacer Lite lacks reactivity features, framework adapters, devtools support, and some of the advanced options that the core utilities have. If that sounds interesting to you, you can
13 changes: 13 additions & 0 deletions examples/vanilla/LiteBatcher/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/emblem-light.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TanStack Pacer - Vanilla Batcher Examples</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/index.ts"></script>
</body>
</html>
17 changes: 17 additions & 0 deletions examples/vanilla/LiteBatcher/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@tanstack/pacer-example-vanilla-lite-batcher",
"private": true,
"type": "module",
"scripts": {
"dev": "vite --port=3005",
"build": "vite build",
"preview": "vite preview",
"test:types": "tsc"
},
"dependencies": {
"@tanstack/pacer-lite": "0.0.1"
},
"devDependencies": {
"vite": "^7.2.2"
}
}
Loading
Loading