Skip to content

Instantly share code, notes, and snippets.

@psenger
Last active May 25, 2025 02:05
Show Gist options
  • Save psenger/e518da07ab24eae36a7ddfb63c96cfb4 to your computer and use it in GitHub Desktop.
Save psenger/e518da07ab24eae36a7ddfb63c96cfb4 to your computer and use it in GitHub Desktop.
[Design Pattern: "Error Handling Wrapper" or "Async Await Wrapper"] #Promise #JavaScript #DesignPattern

Design Pattern: "Error Handling Wrapper" or "Async Await Wrapper"

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.

Advantages of the Pattern

  • 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 Pattern

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];
    }
}

Example Usage / Counter Examples

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
    })
/**
* 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];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment