agentsclimarketplace

Rn async patterns

Skill almasumdev/awesome-react-native-agent-skills/.github/skills/concurrency_and_networking/rn-async-patterns

Curated agent skills, conventions, and workflows for building React Native apps with AI coding agents.

Install
npx -y skills add almasumdev/awesome-react-native-agent-skills --skill rn-async-patterns

Assembled 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.

What its author says it does

Copied from the file, not written here

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.

SKILL.md

4.3 KB, 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.
  • setState inside an async function after unmount. Always guard on signal.aborted.
  • async functions passed directly to useEffect -- the return must be a cleanup function, not a Promise. Declare the async function inside.
  • Swallow abort errors -- check signal.aborted or err.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 mapLimit or p-limit; no unbounded Promise.all over user input.
  • Abort errors are recognised and not reported as real failures.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.