Bring the "Umph" back to the JavaScript error handling!
const result = await try$(itMayThrow(a, b, c));
result.match(
when(Ok, consumeResult),
when(Err, handleError)
);
Or even better, You can use the .dwait
function call on the returned promise of try$
to get a DeferredPromise
built using dwait library:
const result = await try$(itMayThrow(a, b, c)).dwait().match(
when(Ok, consumeResult),
when(Err, handleError)
).await;
With the use of DeferredPromise
we can keep chaining functions instead of awaiting each result, And we can defer all awaits to the last operation.
Does that seem too Rusty to you? What about something like this? Let's Go!
const [res, err] = await try$(itMayThrow(a, b, c));
if (!!err) {
handleError(err);
return;
}
consumeResult(res);
You may say these are all async, What about sync operations? Here are the examples above but this time as sync functions.
const result = tryFn$(itMayThrow, a, b, c);
result.match(
when(Ok, consumeResult),
when(Err, handleError)
);
Or
const [res, err] = tryFn$(itMayThrow, a, b, c);
if (!!err) {
handleError(err);
return;
}
consumeResult(res);
We all have been in places where we want to get some result from a function that can throw, This is especially true for async functions since we usually need async operations to handle IO. So let's take a look at this example:
axios
.get("/user/12345")
.then((result) => consumeResult(result))
.catch((error) => handleError(error));
It is the snippet of code from the ErrorHandling
section of axios
library. So what about async/await
?
If we want to handle the errors properly with the async/await
pattern we have to write it inside a try/catch
block like this:
try {
const resp = await axios.get("/user/12345");
consumeResult(resp);
} catch (err) {
handleError(err);
}
That is still not that bad, Right? What if we want to handle errors by using a default value instead of canceling the operation? Now we have to rewrite our code like this:
let user = null;
try {
user = await axios.get("/user/12345");
} catch {
user = defaultUser;
}
consumeResult(user);
How about when we would like to load some additional data based on the first request? So let's modify the code above to load the user avatar too.
let user = null;
let avatar = null;
try {
user = await axios.get("/user/12345");
avatar = await axios.get(user.avatarImage);
} catch {
if (!user) {
user = defaultUser;
}
if (!avatar) {
avatar = defaultAvatar;
}
}
consumeResult(user, avatar);
As you can see it really easily gets out of hand... So let's rewrite it using tryumph
!
const userResult = await try$(axios.get("/user/12345"));
const user = userResult.unwrapOr(defaultUser);
const avatarResult = await try$(axios.get(user.avatarImage));
const avatar = avatarResult.unwrapOr(defaultAvatar);
consumeResult(user, avatar);
Much cleaner huh? tryumph
borrows heavily from Rust
and Go
error handling to provide a more sane way for keeping errors under control!
You can read the documentation over here.
What is in Result?
const a = await try$(itWillBeFine());
console.log(result.isOk()); // true
console.log(result.isErr()); // false
console.log(result.ok()); // "Result"
console.log(result.error()); // undefined
console.log(result.unwrap()); // "Result"
console.log(result.unwrapOr("Default")); // "Result"
const b = await try$(itWillThrow());
console.log(result.isOk()); // false
console.log(result.isErr()); // true
console.log(result.ok()); // undefined
console.log(result.error()); // "Error"
console.log(result.unwrap()); // CRASH!! it will throw the error!
console.log(result.unwrapOr("Default")); // "Default"
Here is another sync example
const sum = (a, b) => a + b;
const [res, err] = tryFn$(sum, 1, 2);
if (!!err) {
handleError(err);
return;
}
consumeResult(res);