- xrays design
xrays
is designed to catch errors thrown by async functions.
xrays
has a few simple goals
- Reduce total lines of code up to 50%
North Star Metric
- Reduce total levels of nesting up to 30%
- Improve developer happiness up to by 20%
Today, there are a two main patterns used for error handling of async functions in JavaScript & Typescript.
Promise Chaining
try / catch
withasync / await
Basic Promise Chaining works as such: 3 Lines of Code Below, 1 Level of Nesting
// An Example of Basic Promise Chaining
asyncFunction()
.then((value) => { console.log(value) })
.catch((error) => { console.log('Whoops Ran into an Error') })
While it can work well for simple use cases, it has 2 major drawbacks
The first is that dependent async function calls requires deeply nested code, even without Error Handling.
The following scenario provides a great practical example:
Scenario — Resource CRUD
1. Make a Search API Call to Search for an Existing Resource
2. Choose if we should Create a New Resource (Insert) or Update an Existing One (Update)
3. Notify Websocket Subscribers via Push about New Resource Information
Promise Chaining without Error Handling: 17 Lines of Code Below, 6 Levels of Nesting
// An Example of Promise Chaining without Error Handling for the Resource CRUD Scenario
searchAPICall()
.then((searchData) => {
if (shouldCreateNewResource(searchData)) {
insertAPICall(searchData).then((insertResponse) => {
pushToSubscribers(insertResponse)
.then((subscriberResponses) => {
console.log(subscriberResponses);
});
});
} else {
updateAPICall(searchData).then((updateResponse) => {
pushToSubscribers(updateResponse)
.then((subscriberResponses) => {
console.log(subscriberResponses);
});
});
}
});
Adding error handling to each async call to send proper error messages to users results in further nested complexity.
Promise Chaining with Error Handling: 32 Lines of Code Below, 6 Levels of Nesting
// An Example of Promise Chaining with Error Handling for the Resource CRUD Scenario
searchAPICall()
.then((searchData) => {
if (shouldCreateNewResource(searchData)) {
insertAPICall(searchData).then((insertResponse) => {
pushToSubscribers(insertResponse)
.then((subscriberResponses) => {
console.log(subscriberResponses);
})
.catch((pushError) => {
sendErrorResponseToUser('Push Error', pushError);
});
})
.catch((insertError) => {
sendErrorResponseToUser('Insert Error', insertError);
});
} else {
updateAPICall(searchData).then((updateResponse) => {
pushToSubscribers(updateResponse)
.then((subscriberResponses) => {
console.log(subscriberResponses);
})
.catch((pushError) => {
sendErrorResponseToUser('Push Error', pushError);
});
})
.catch((updateError) => {
sendErrorResponseToUser('Update Error', updateError);
});
}
})
.catch((searchError) => {
sendErrorResponseToUser('Search Error', searchError);
});
Basic try / catch
with async / await
works as such:
6 Lines of Code Below, 1 Level of Nesting
// An Basic Example of try / catch with async / await
try {
const value = await asyncFunction();
console.log(value);
} catch (error) {
console.log('Whoops Ran into an Error');
}
try / catch
with async / await
suffers from the same drawbacks as Promise Chaining
but to a lesser degree.
try / catch
& async / await
without Error Handling:
10 Lines of Code Below, 1 Level of Nesting
// An Example of try / catch with async / await without Error Handling for the Resource CRUD Scenario
const searchData = await searchAPICall();
let insertResponse, updateResponse;
if (shouldCreateNewResource(searchData)) {
insertResponse = await insertAPICall(searchData);
const subscriberResponses = await pushToSubscribers(insertResponse);
console.log(subscriberResponses);
} else {
updateResponse = await updateAPICall(searchData);
const subscriberResponses = await pushToSubscribers(updateResponse);
console.log(subscriberResponses);
}
try / catch
& async / await
with Error Handling:
30 Lines of Code Below, 3 Levels of Nesting
// An Example of try / catch with async / await with Error Handling for the Resource CRUD Scenario
try {
const searchData = await searchAPICall();
if (shouldCreateNewResource(searchData)) {
try {
const insertResponse = await insertAPICall(searchData);
} catch (insertError) {
sendErrorResponseToUser('Insert Error', insertError);
}
try {
const subscriberResponses = await pushToSubscribers(insertResponse);
console.log(subscriberResponses);
} catch (pushError) {
sendErrorResponseToUser('Push Error', pushError);
}
} else {
try {
const updateResponse = await updateAPICall(searchData);
} catch (updateError) {
sendErrorResponseToUser('Update Error', updateError);
}
try {
const subscriberResponses = await pushToSubscribers(updateResponse);
console.log(subscriberResponses);
} catch (pushError) {
sendErrorResponseToUser('Push Error', pushError);
}
}
} catch (searchError) {
sendErrorResponseToUser('Search Error', searchError);
}
A basic call with xrays
works as such:
3 Lines of Code Below, 0 Levels of Nesting
// A Basic Example of xrays
const { data: value, error } = await x(asyncFunction);
if (error) console.log('Whoops Ran into an Error');
console.log(value);
The benefits of xrays
scale to the use cases where Promise Chaining
& try / catch
with async / await
fail.
xrays
without Error Handling:
10 Lines of Code Below, 1 Level of Nesting
// An Example of xrays without Error Handling for the Resource CRUD Scenario
const { data: searchData } = await x(searchAPICall);
if (shouldCreateNewResource(searchData)) {
const { data: insertResponse } = await x(insertAPICall, searchData);
const { data: subscriberResponses } = await x(pushToSubscribers, insertResponse);
console.log(subscriberResponses);
} else {
const { data: updateResponse } = await x(updateAPICall, searchData);
const { data: subscriberResponses } = await x(pushToSubscribers, updateResponse);
console.log(subscriberResponses);
}
xrays
with Error Handling:
15 Lines of Code Below, 1 Level of Nesting
// An Example of xrays with Error Handling for the Resource CRUD Scenario
const { data: searchData, error: searchError } = await x(searchAPICall);
if (searchError) sendErrorResponseToUser('Search Error', searchError);
if (shouldCreateNewResource(searchData)) {
const { data: insertResponse, error: insertError } = await x(insertAPICall, searchData);
if (insertError) sendErrorResponseToUser('Insert Error', insertError);
const { data: subscriberResponses, error: pushError } = await x(pushToSubscribers, insertResponse);
if (pushError) sendErrorResponseToUser('Push Error', pushError);
console.log(subscriberResponses);
} else {
const { data: updateResponse, error: updateError } = await x(updateAPICall, searchData);
if (updateError) sendErrorResponseToUser('Update Error', updateError);
const { data: subscriberResponses, error: pushError } = await x(pushToSubscribers, updateResponse);
if (pushError) sendErrorResponseToUser('Push Error', pushError);
console.log(subscriberResponses);
}
Writing Code with Error Handling uses less lines of code than Promise Chaining uses without Error Handling for the same functionality.
Error Handling Pattern | LoC | LoN | LoC w/ Error Handling | LoN w/ Error Handling |
---|---|---|---|---|
Promise Chaining |
17 | 6 | 32 | 6 |
try / catch |
10 | 1 | 30 | 3 |
xrays |
10 | 1 | 15 | 1 |
LoC
= Lines of Code
LoN
= Levels of Nesting
The npm that makes your life simple when writing error handling code.
xrays
catches errors before they become fatal using 3F principle :
- Fail-Fast Pattern that Processes Errors before Data
- Flattened Error Handling with just 1 Level of Nesting
- Friendly Error Messages and simpler error handling logic
xrays
is not just for new projects, it makes your existing code better:
- Fractional Effort (and LoC) to add missing Error Handling
- Fast Refactoring through the use of Consistent Syntax
xrays
is a part of the Open Source Universe. All Projects in the Open Source Universe follow the same Contribution Guidelines & Process.
We would love for you to contribute to the Open Source Universe!
Also check out the rewards offered for contributing to the Open Source Universe.
xrays
only plans to initially support Typescript & JavaScript Applications. However, we would love for the community to contribute versions for other languages (Java, Go, etc.)
When creating an implementation for another language, we encourage that the implementation follow the Open Source Universe Principles & follows the same design (this document) & specifications (unit tests) as the Typescript & JavaScript Implementation.