Overview

ZeroPromise<T> is a Promise-compatible wrapper with explicit lifecycle: active → resolved | rejected | cancelled. It exposes .signal (AbortSignal) and .cancel() so fetchers and UI can abort in-flight work.

Mates HTTP helpers return ZeroPromise. asyncAtom / asyncAction inject the current run as { zero } so you can pass zero.signal into fetch or call zero.cancel().

ZeroPromise vs Promise

API
Cancel
State
Promise No built-in cancel Settles once; no AbortSignal.
ZeroPromise .cancel() + .signal isActive / isResolved / isRejected / isCancelled.

Resolve vs cancel

Start a 2s ZeroPromise, then cancel before it resolves. Status tracks active / resolved / cancelled.

import { html, atom, ZeroPromise, renderApp } from 'mates';

const App = () => {
const status = atom('idle');
const result = atom('');
let current = null;

const start = () => {
current = new ZeroPromise();
status.set('active');
result.set('');
const timer = setTimeout(() => {
if (current && current.isActive()) {
current.resolve('finished after 2s');
}
}, 2000);
current
.then((v) => { status.set('resolved'); result.set(v); })
.catch((e) => {
clearTimeout(timer);
status.set(e.name === 'AbortError' ? 'cancelled' : 'rejected');
result.set(e.message || String(e));
});
};

const cancel = () => { current?.cancel(); };

return () => html`
<div class="m-col m-gap">
<div class="m-flex m-items-center m-justify-center m-gap-sm">
<button class="btn-primary" @click=${start} ?disabled=${status() === 'active'}>
Start 2s work
</button>
<button class="btn-danger" @click=${cancel} ?disabled=${status() !== 'active'}>
Cancel
</button>

AbortSignal + fetch

Pass zero.signal into fetch. Cancel aborts the network request.

import { html, atom, ZeroPromise, renderApp } from 'mates';

const App = () => {
const note = atom('Idle');
let current = null;

const run = () => {
current = new ZeroPromise();
note.set('fetching…');
fetch('https://jsonplaceholder.typicode.com/todos/1', {
signal: current.signal,
})
.then((r) => r.json())
.then((todo) => {
current.resolve(todo.title);
note.set(todo.title);
})
.catch((e) => {
if (e.name === 'AbortError') note.set('aborted');
else note.set(e.message);
current.reject(e);
});
};

return () => html`
<div class="m-col m-gap">
<div class="m-flex m-items-center m-justify-center m-gap-sm">
<button class="btn-primary" @click=${run}>Fetch with signal</button>
<button class="btn-danger" @click=${() => current?.cancel()}>Abort</button>
</div>
<p class="muted">${note()}</p>
</div>
`;
};
renderApp(App, document.getElementById('app'));