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 🙏

© 2026 – Pkg Stats / Ryan Hefner

async-polling-task-manage

v1.0.0

Published

A React hook for managing async polling tasks with persistence, batch support, and automatic retry.

Readme

async-polling-task-manage

A lightweight React hook for managing async polling tasks — with persistence, batch support, and automatic retry.

Ideal for scenarios where you submit a backend task and need to poll its status until completion, even across page refreshes.

Features

  • React Hook — Simple usePollingTask hook with minimal API surface
  • Persistence — Task state is saved to localStorage; unfinished tasks automatically resume after page refresh
  • Batch Polling — Start multiple tasks in one call with startBatch
  • Stateless Business Logic — You only provide a polling function; the hook manages all state internally
  • Configurable — Interval, max attempts, timeout, error retry, all customizable per-task
  • TypeScript — Full type definitions included

Install

npm install async-polling-task-manage

Quick Start

import { usePollingTask } from "async-polling-task-manage";

function TaskPanel() {
  const { tasks, start, startBatch, stop, isAnyPolling } = usePollingTask({
    pollingFn: async (taskId) => {
      const res = await fetch(`/api/tasks/${taskId}/status`);
      const data = await res.json();
      return {
        done: data.status === "completed" || data.status === "failed",
        data,
      };
    },
    defaultOptions: {
      interval: 2000,
    },
  });

  return (
    <div>
      <button onClick={() => start("task-1")}>Start Task 1</button>
      <button onClick={() => startBatch(["task-2", "task-3", "task-4"])}>
        Start Batch
      </button>

      {Object.values(tasks).map((task) => (
        <div key={task.taskId}>
          <span>{task.taskId}</span>
          <span>{task.status}</span>
          <span>{JSON.stringify(task.result)}</span>
          {task.status === "polling" && (
            <button onClick={() => stop(task.taskId)}>Stop</button>
          )}
        </div>
      ))}

      {isAnyPolling && <p>Polling in progress...</p>}
    </div>
  );
}

API

usePollingTask<TResult>(config)

Config

| Property | Type | Required | Description | | ---------------- | ----------------------------------------------- | -------- | ------------------------------------------------- | | pollingFn | (taskId: string) => Promise<PollResponse<TResult>> | Yes | The function called on each poll cycle | | namespace | string | No | Isolate task groups in storage (default: "default") | | defaultOptions | PollingOptions | No | Default options applied to all tasks |

PollResponse<TResult>

interface PollResponse<TResult> {
  done: boolean;      // true = task finished, stop polling
  data: TResult | null; // payload to store as result
}

PollingOptions

| Option | Type | Default | Description | | -------------- | --------- | ---------- | --------------------------------------------------- | | interval | number | 3000 | Polling interval in milliseconds | | maxAttempts | number | Infinity | Max poll attempts before timeout | | timeout | number | Infinity | Max total time (ms) before timeout | | persistent | boolean | true | Persist task state in localStorage | | retryOnError | number | 3 | Consecutive errors allowed before marking as error |

Return Value

| Property | Type | Description | | -------------- | ------------------------------------------------- | ------------------------------------ | | tasks | Record<string, TaskState<TResult>> | All task states, keyed by taskId | | start | (taskId: string, options?: PollingOptions) => void | Start polling a single task | | startBatch | (taskIds: string[], options?: PollingOptions) => void | Start polling multiple tasks | | stop | (taskId: string) => void | Stop polling a task (keeps state) | | stopAll | () => void | Stop all active polling | | remove | (taskId: string) => void | Remove a task's state entirely | | clear | () => void | Clear all tasks and storage | | isAnyPolling | boolean | Whether any task is currently polling |

TaskState<TResult>

interface TaskState<TResult> {
  taskId: string;
  status: "idle" | "polling" | "success" | "error" | "timeout";
  result: TResult | null;
  error: Error | null;
  attempts: number;
  createdAt: number;
  lastPolledAt: number | null;
}

Advanced Usage

Namespace Isolation

Use namespace to separate different groups of tasks. Each namespace has its own storage key.

const imageJobs = usePollingTask({
  namespace: "image-processing",
  pollingFn: checkImageStatus,
});

const videoJobs = usePollingTask({
  namespace: "video-processing",
  pollingFn: checkVideoStatus,
});

Per-Task Options

Override default options for individual tasks:

start("urgent-task", {
  interval: 500,
  maxAttempts: 100,
  retryOnError: 5,
});

Non-Persistent Tasks

Disable persistence for tasks that don't need to survive refreshes:

start("ephemeral-task", { persistent: false });

Page Refresh Recovery

Tasks with status: "polling" are automatically resumed when the hook mounts. No extra code needed — just use the same namespace and pollingFn.

License

MIT