tryThis

A safe wrapper that catches errors from synchronous functions, async functions, and raw promises, returning a Go-style [error, result] tuple instead of throwing. Never write a try/catch block again.

Quick Start

Wrap any operation in Aura.tryThis() and destructure the result. If the operation succeeds, error is null. If it fails, result is null and error is an Error instance.

js
// Sync
const [err, data] = Aura.tryThis(() => JSON.parse(rawString));

// Async
const [err, users] = await Aura.tryThis(() =>
  fetch('/api/users').then(r => r.json())
);

// Direct promise
const [err, res] = await Aura.tryThis(fetch('/api/users'));

Signature

Aura.tryThis<T>(fnOrPromise: (() => T | Promise<T>) | Promise<T>): TryResult<T> | Promise<TryResult<T>>
ParamTypeDescription
fnOrPromise(() => T | Promise<T>) | Promise<T>A sync function, an async function, or a Promise to wrap.

Return Value

ScenarioReturn TypeValue
Sync success[null, T]Synchronous tuple with the result.
Sync failure[Error, null]Synchronous tuple with the caught error.
Async successPromise<[null, T]>Resolves with the result.
Async failurePromise<[Error, null]>Resolves (not rejects) with the error.

Non-Error throws are automatically wrapped via new Error(String(thrown)), so the first element is always an Error instance or null.

The Tuple Pattern

The [error, result] tuple eliminates the need for try/catch blocks and makes error handling explicit at the call site. It is inspired by Go's multiple return values.

js
// Before — nested try/catch
let config;
try {
  config = JSON.parse(raw);
} catch (e) {
  config = defaultConfig;
}

// After — flat and readable
const [err, config] = Aura.tryThis(() => JSON.parse(raw));
if (err) config = defaultConfig;

Key rules:

Sync Functions

When you pass a function that returns a non-Promise value, tryThis returns a tuple synchronously — no await needed.

js
// Success
const [err, num] = Aura.tryThis(() => parseInt('42', 10));
console.log(err);  // null
console.log(num);  // 42

// Failure
const [err, val] = Aura.tryThis(() => {
  throw new Error('boom');
});
console.log(err.message); // 'boom'
console.log(val);          // null

Async Functions

When the function returns a Promise (or is declared async), tryThis returns a Promise that resolves to the tuple. Use await to unwrap it.

js
const [err, users] = await Aura.tryThis(async () => {
  const res = await fetch('/api/users');
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
});

if (err) {
  Aura.log.error('Failed to load users', err);
} else {
  renderUsers(users);
}

Direct Promises

You can also pass a Promise directly instead of wrapping it in a function. This is handy when you already have a Promise reference.

js
const promise = fetch('/api/config').then(r => r.json());

// Pass the promise directly
const [err, config] = await Aura.tryThis(promise);

if (err) {
  console.log('Using defaults', err.message);
}

Recipe: JSON Parsing

Safely parse JSON without try/catch. Perfect for user input, localStorage, or WebSocket messages.

js
function safeParse(raw, fallback = null) {
  const [err, data] = Aura.tryThis(() => JSON.parse(raw));
  return err ? fallback : data;
}

// Usage
const settings = safeParse(localStorage.getItem('settings'), {});
const message  = safeParse(wsEvent.data, { type: 'unknown' });

Recipe: API Calls

Combine tryThis with fetch for clean, flat async error handling.

js
async function getUser(id) {
  const [fetchErr, res] = await Aura.tryThis(
    fetch(`/api/users/${id}`)
  );
  if (fetchErr) return { error: 'Network failure' };

  if (!res.ok) return { error: `HTTP ${res.status}` };

  const [parseErr, user] = await Aura.tryThis(res.json());
  if (parseErr) return { error: 'Invalid JSON' };

  return { data: user };
}

Recipe: File Reading

Read files via the File API without nested callbacks or try/catch.

js
async function readFile(file) {
  const [err, text] = await Aura.tryThis(
    new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = () => resolve(reader.result);
      reader.onerror = () => reject(reader.error);
      reader.readAsText(file);
    })
  );

  if (err) {
    console.error('Could not read file:', err.message);
    return null;
  }

  return text;
}

Recipe: Chaining with Logger

Pair tryThis with Aura.log for structured error reporting without cluttering your business logic.

js
const api = Aura.log.tag('API');

async function loadDashboard() {
  const [err, data] = await Aura.tryThis(async () => {
    const res = await fetch('/api/dashboard');
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return res.json();
  });

  if (err) {
    api.error('Dashboard load failed', err);
    showFallbackUI();
    return;
  }

  api.info('Dashboard loaded', { widgets: data.widgets.length });
  renderDashboard(data);
}

// Sync + Logger
const [parseErr, config] = Aura.tryThis(() =>
  JSON.parse(localStorage.getItem('config'))
);

if (parseErr) {
  Aura.log.warn('Config parse failed, using defaults', parseErr);
}

// Export errors for debugging
const errorLog = Aura.log.export('error');
console.log(errorLog);