The cleanest pattern is wrapping with try/catch:
async function fetchData() {
try {
const res = await fetch('https://api.example.com/data');
if (!res.ok) throw new Error(`HTTP error: ${res.status}`);
return await res.json();
} catch (err) {
console.error('fetchData failed:', err);
throw err; // re-throw if the caller needs to handle it
}
}
If you want to avoid try/catch everywhere, you can write a small wrapper:
const to = (p) => p.then(data => [null, data]).catch(err => [err, null]);
const [err, data] = await to(fetchData());
if (err) { /* handle */ }
The to() pattern (from go-style error handling) is popular in large codebases because it keeps error handling explicit without nesting.