Handle async errors without nesting try / catch

Concept of async / await
keywords made code much more readable and maintainable in comparison of callback hell
but still we need to handle errors for async operations.
Usual try / catch
blocks will make a code nested, create new closure making variables created inside inaccessible outside of try / catch
.
To avoid new closure and make code flat (all rows are on the same level of depth) for async / await
we can use a simple util calm
Solution goals
const [value, error] = await calm(fetch('https://api.my.app/endpoint'));
if (error || !value) {
// handle case
return;
}
operate(value);
Implementation
type ThenArg<T> = T extends PromiseLike<infer U> ? U : T;
const calm = async <P extends Promise<any>>(p: P): Promise<[ThenArg<P> | undefined, Error | undefined]> => {
try {
const value = await p;
return [value, undefined];
} catch (error) {
return [undefined, error];
}
}