Overview

taskAction manages a concurrent task queue. You add items, control concurrency, and track per-task status, progress, and results — all reactively. Ideal for file uploads, imports, offline sync replay, or any workflow where multiple async jobs should be orchestrated together.

Each task receives an AbortSignal as its second argument so cancellation via stop() (or per-task abort) can cleanly unwind in-flight work. Derived getters like runningTasks() and successTasks() let you drive progress UI without manual bookkeeping.

vs asyncAction

Capability
asyncAction
taskAction
Unit of work Single invocation Queue of many items with per-task lifecycle.
Concurrency One in-flight (latest wins / cancel) Configurable concurrency (serial by default, or N at once).
Progress isLoading / status progress() 0–100 + filtered task lists by status.
Cancellation cancel() drops result stop() / per-task AbortSignal for real abort.

taskAction — queue, concurrency, pause, delete

clearAndStart with concurrency 2. addAndStart appends while running. pause() lets in-flight tasks finish; start() resumes; stop() aborts via the task signal; delete() removes one task.

import { html, taskAction, nothing, renderApp } from 'mates';

const processTask = taskAction(async (item, signal) => {
const ms = 800 + Math.random() * 1200;
await new Promise((resolve, reject) => {
const id = setTimeout(resolve, ms);
signal.addEventListener('abort', () => {
clearTimeout(id);
reject(new Error('cancelled'));
}, { once: true });
});
if (item.name.endsWith('.fail')) throw new Error('failed');
return { processed: item.name, duration: Math.round(ms) };
});

const ITEMS = [
{ id: 1, name: 'document.pdf' },
{ id: 2, name: 'image.png' },
{ id: 3, name: 'video.mp4' },
{ id: 4, name: 'data.csv' },
{ id: 5, name: 'report.xlsx' },
];

let extra = 6;

const statusColor = {
pending: '#64748b', running: '#f59e0b',
success: '#22c55e', failed: '#ef4444', cancelled: '#94a3b8',
};

const App = () => {
return () => html`
<div class="m-col m-gap">
<div class="m-flex m-flex-wrap m-gap-sm m-items-center">
<button class="btn-primary"
?disabled=${processTask.isRunning()}