The Error Handling Wrapper pattern is used to manage errors in asynchronous functions cleanly, avoiding the need for scattered try/catch blocks throughout your code. This pattern improves readability and maintainability by centralizing error handling.
- Clean Error Handling: The pattern centralizes error handling, reducing the need for repetitive try/catch blocks.
- Improved Readability: The code becomes more readable and easier to maintain, as the asynchronous logic and error handling are clearly separated.
- Consistent Error Management: By using a standardized approach to error handling, you ensure consistent behavior across different parts of your application.
The core of this pattern is a function that wraps a promise and returns a tuple containing the resolved data or the error. Here’s the
implementation:
/**
* asyncWrap is an error handling wrapper for asynchronous functions.
* It takes a promise as input and returns a tuple where the first element
* is the resolved data and the second element is the error, if any.
* This helps in handling errors without using try/catch blocks.
*
* @param {Promise} promise - The promise to be resolved.
* @returns {Array} - An array with two elements: [data, error]
* data: the resolved value from the promise
* error: the error caught from the promise
*/
async function asyncWrap(promise) {
try {
const data = await promise;
return [data, null];
} catch (err) {
return [null, err];
}
}
Below is an mixed example demonstrating the use of the asyncWrap function in an asynchronous operation and others.
function simulatePromise(shouldResolve) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldResolve) {
resolve('Data fetched successfully');
} else {
reject('Failed to fetch data');
}
}, 1000);
});
}
// ------------------
// Method 1: Chaining
// ------------------
// Old School, causes Scope complexity as variables need to be lifted or scoped.
/**
* Output:
* Compete Data fetched successfully
* finally
*/
simulatePromise(true)
.then(data => {
console.log('Compete', data)
})
.catch(err => {
console.error('Error',err)
})
.finally(() => {
console.info('finally')
})
/**
* Output:
* Error Failed to fetch data
* finally
*/
simulatePromise(false)
.then(data => {
console.log('Compete', data)
})
.catch(err => {
console.error('Error',err)
})
.finally(() => {
console.info('finally')
})
const run = async () => {
let data
// ---------------------------
// Method 2: Try-Catch-Finally
// ---------------------------
// Much new with Async Await, works great, but again, can raise
// complexity with scope and variables. additionally cause
// many Try-Catch-Final blocks.
/**
* Output:
* Method 2 - true - Complete Data fetched successfully
* Method 2 - true - Finally
*/
try {
data = await simulatePromise(true);
console.log('Method 2 - true - Complete', data)
} catch (err) {
console.error('Method 2 - true - Error',err)
} finally {
console.info('Method 2 - true - Finally')
}
/**
* Output:
* Method 2 - false - Error Failed to fetch data
* Method 2 - false - Finally
*/
try {
data = await simulatePromise(false);
console.log('Method 2 - false - Complete', data)
} catch (err) {
console.error('Method 2 - false - Error',err)
} finally {
console.info('Method 2 - false - Finally')
}
// ---------------------------
// Method 3: Mixed-Veg
// ---------------------------
// Controversial, as it is mixed, this should be
// avoided in my humble professional opinion avoided.
/**
* Output:
* Method 3 - true - Finally
* Method 3 - true - Complete Data fetched successfully
*/
data = await simulatePromise(true).catch(err => {
console.error('Method 3 - true - Error',err)
}).finally(() => {
console.info('Method 3 - true - Finally')
})
console.log('Method 3 - true - Complete', data)
/**
* Output:
* Method 3 - false - Error Failed to fetch data
* Method 3 - false - Finally
* Method 3 - false - Complete undefined
*/
data = await simulatePromise(false).catch(err => {
console.error('Method 3 - false - Error',err)
}).finally(() => {
console.info('Method 3 - false - Finally')
})
console.log('Method 3 - false - Complete', data)
// ---------------------------
// Method 4: xxx
// ---------------------------
// Using asyncWrap to handle promise resolution and rejection
/**
* Output:
* Method 4 - true - Success: Data fetched successfully
*/
const [d1, err1] = await asyncWrap(simulatePromise(true));
if (err1) {
console.error('Method 4 - true - Error:', err1);
} else {
console.log('Method 4 - true - Success:', d1);
}
/**
* Output:
* Method 4 - false - Error: Failed to fetch data
*/
const [d2, err2] = await asyncWrap(simulatePromise(false));
if (err2) {
console.error('Method 4 - false - Error:', err2);
} else {
console.log('Method 4 - false - Success:', d2);
}
}
run()
.then(()=>{
console.log('run - Done')
process.exit(0) // zero exit, all good
})
.catch(()=>{
console.log('run - Failed')
process.exit(1) // non-zero exit, bad
})