Rn async patterns
Expert guidance on Promises, async/await, cancellation with AbortController, timeouts, and retries in React Native / TypeScript. Use when asked about async code, cancellation, timeouts, or retries.From its SKILL.md
npx -y skills add almasumdev/awesome-react-native-agent-skills --skill rn-async-patternsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
4.3 KB, ~1.1k tokens by cl100k_base, as published. Nobody here has run it
React Native Async Patterns
Instructions
Always use async/await with typed errors and AbortController for cancellation. Never leak promises whose resolution outlives the component.
1. Cancellable Fetch
// src/shared/lib/http.ts
export class HttpError extends Error {
constructor(public readonly status: number, public readonly url: string, msg: string) {
super(msg);
this.name = 'HttpError';
}
}
export async function getJson<T>(url: string, signal?: AbortSignal): Promise<T> {
const res = await fetch(url, { signal });
if (!res.ok) throw new HttpError(res.status, url, `HTTP ${res.status}`);
return (await res.json()) as T;
}
2. Effect + AbortController
import { useEffect, useState } from 'react';
import { getJson } from '@shared/lib/http';
import type { Article } from '@domain/articles';
export function useLoadArticle(id: string) {
const [data, setData] = useState<Article | null>(null);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const ac = new AbortController();
getJson<Article>(`/articles/${id}`, ac.signal)
.then(setData)
.catch((e: unknown) => {
if (ac.signal.aborted) return;
setError(e instanceof Error ? e : new Error(String(e)));
});
return () => ac.abort();
}, [id]);
return { data, error };
}
3. Timeout
export function withTimeout<T>(p: Promise<T>, ms: number, ac?: AbortController): Promise<T> {
return new Promise<T>((resolve, reject) => {
const t = setTimeout(() => {
ac?.abort();
reject(new Error(`timeout after ${ms}ms`));
}, ms);
p.then(
(v) => {
clearTimeout(t);
resolve(v);
},
(e) => {
clearTimeout(t);
reject(e);
},
);
});
}
Usage:
const ac = new AbortController();
const data = await withTimeout(getJson<Article>('/articles/1', ac.signal), 5_000, ac);
4. Retry with Backoff
export async function retry<T>(
fn: (signal: AbortSignal) => Promise<T>,
opts: { attempts?: number; baseMs?: number; signal?: AbortSignal } = {},
): Promise<T> {
const { attempts = 3, baseMs = 300, signal } = opts;
let lastErr: unknown;
for (let i = 0; i < attempts; i++) {
if (signal?.aborted) throw new Error('aborted');
try {
return await fn(signal ?? new AbortController().signal);
} catch (e) {
lastErr = e;
const delay = baseMs * 2 ** i + Math.random() * baseMs;
await new Promise((r) => setTimeout(r, delay));
}
}
throw lastErr;
}
Do not retry on 4xx responses (client errors). Add a predicate argument if needed:
const shouldRetry = (e: unknown) =>
e instanceof HttpError ? e.status >= 500 : true;
5. Parallelism Control
For a bounded batch, use Promise.all with chunking:
export async function mapLimit<I, O>(
items: readonly I[],
limit: number,
fn: (item: I, index: number) => Promise<O>,
): Promise<O[]> {
const out: O[] = new Array(items.length);
let i = 0;
async function worker() {
while (i < items.length) {
const idx = i++;
out[idx] = await fn(items[idx]!, idx);
}
}
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
return out;
}
6. Never Do This
- Fire-and-forget
.then()chains in effects without cancellation. setStateinside an async function after unmount. Always guard onsignal.aborted.asyncfunctions passed directly touseEffect-- the return must be a cleanup function, not a Promise. Declare the async function inside.- Swallow abort errors -- check
signal.abortedorerr.name === 'AbortError'and return.
Checklist
- Every network call accepts and forwards an
AbortSignal. - Effects that start async work return a cleanup that aborts it.
- Timeouts use a combined pattern that also aborts the underlying request.
- Retries use exponential backoff with jitter and skip 4xx responses.
- Bounded concurrency uses
mapLimitorp-limit; no unboundedPromise.allover user input. - Abort errors are recognised and not reported as real failures.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.