Safe Requests: Go/Rust-Style Tuples

Eliminate nested try / catch boilerplate across your codebase with the dedicated api.safe.* namespace.


The Problem with Traditional try / catch

In JavaScript and TypeScript, catch (e: unknown) loses type safety. Variables declared inside the try block are scoped locally, forcing developers to declare loose let data: MyType | undefined outside the block.

The api.safe namespace returns a Promise<SafeResult<T>>—a strongly typed tuple representing either [data, null] or [null, FetchNativeError].

Safe GET Requests

safe-get.ts
typescript
import { createClient } from 'react-native-fetch-client';

const api = createClient({ baseURL: 'https://api.example.com' });

interface Product {
  id: string;
  name: string;
  price: number;
}

// Returns [data, null] on success, or [null, error] on failure
const [product, error] = await api.safe.get<Product>('/products/123');

if (error) {
  // TypeScript narrows error to FetchNativeError
  console.error('Could not load product:', error.message);
  showErrorToast(error.message);
  return;
}

// TypeScript narrows product to Product (non-null!)
console.log('Product loaded:', product.name, product.price);

Safe Mutations (POST, PUT, PATCH, DELETE)

All HTTP verbs are available on the api.safe object:

typescript
// Safe POST mutation
const [created, postErr] = await api.safe.post('/products', {
  name: 'Mechanical Keyboard',
  price: 99.99,
});

if (postErr) {
  alert('Creation failed: ' + postErr.message);
  return;
}

// Safe DELETE mutation
const [deleted, delErr] = await api.safe.delete('/products/123');
TypeScript Discriminated Union
Because the tuple is typed as [T, null] | [null, FetchNativeError], checking if (error) return; automatically narrows data from T | null to T with 100% type soundness!