ajel
What is ajel?
ajel is a set of thin wrappers for try-catch, coupled with eslint rules to encourage better error handling.
Installation
yarn add ajel eslint-plugin-ajel
pnpm add ajel eslint-plugin-ajel
Usage
// Handling async functions that throw
import { ajel } from 'ajel';
async function main() {
const result = await ajel(Promise.resolve('hello world'));
// Accessing result before narrowing union type throws eslint error
// console.log(result);
if (result instanceof Error) {
// This return narrows the type of result to its Result
return;
}
return result;
}
// Handling syncronous functions that throw
import { sjel } from 'ajel';
async function main() {
const result = sjel(JSON.parse, "{}");
// Accessing result before narrowing union type throws eslint error
// console.log(result);
if (result instanceof Error) {
// This return narrows the type of result to its Result
return;
}
return result;
}
ajel
and sjel
are thin wrappers for try-catch, coupled with eslint rules to encourage better error handling.
The linting tools are available in the package eslint-plugin-ajel
Basic eslintrc
{
plugins: ['ajel'],
extends: [
'plugin:ajel/recommended',
],
}
Why I created AJEL
As I entered yet another year of employment at my current company, I was tasked with a new goal. I wanted to learn something new. A little mustached Netflix birdie told me to dig one level deeper. I told myself it was time to stop being a lowly Typescript soy latte dev, and alas, I had a brief but impactful affair with the oxidized language Rust. As work demands kicked into overdrive during the busy portion of the year, I returned to Typescript land with my new experience with Result types - and I saw nothing but holes everywhere.
It greatly bothered me that I had never fully considered how to properly handle errors in my code. Thus began my
exploration of the topic.
It was not hard to narrow down the source of the problem. try/catch
is a terrible way to handle errors. More specifically, the problem is the throw
keyword is
not included in the type system, and thus, it is not possible to know what errors a function can throw.
I tried a few different approaches. Result-type libraries felt like trying to force Rust's paradigm into a language that wasn't designed for it. I wanted to find a way to with the language, not against it.
More Advanced Example
import { ajel, sjel } from 'ajel';
class CustomError extends Error { }
class CustomError2 extends Error { }
class CustomError3 extends Error { }
const foobarFn = async () => {
const result = await ajel(Promise.resolve('result'));
const result2 = sjel(JSON.parse, '{}');
const result3 = await ajel(Promise.resolve('result'));
const result5 = sjel((stringvar: string) => stringvar, '{}');
//------ Test2 SJEL
if (result2 instanceof CustomError) {
//We can access the error here in BinaryExpression with var instanceof
console.log(result2);
return;
}
// Cant Access the result2 variable here
// console.log(result2);
if (result2 instanceof Error) {
console.log(result2);
// This return narrows the type of result2 to its Result
return;
}
// Type is narrowed - no longer a union of Result | Error -> just Result
console.log(result2);
//------ Test AJEL
// Cant Access the result variable here
// console.log(result);
switch (true) {
case result instanceof CustomError3:
//We can access the error here in BinaryExpression with var instanceof
console.log(test);
break;
//We support fall through
case result instanceof CustomError2:
case result instanceof CustomError:
console.log(result);
break;
case result instanceof Error:
break;
}
console.log(result);
//---- No handling of AJEL and SJEL returns reports Errors
// console.log(result3);
// console.log(result5);
};