Last active
October 13, 2021 23:08
-
-
Save theScottyJam/fee1a93db153d87fbbf3d45fc0241c49 to your computer and use it in GitHub Desktop.
Light version of the explicit-exceptions API. Designed for those would rather maintain a simplified version of the library than add it as a dependency. This gist is licensed under the MIT license.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict' | |
// This module provides a light version of the explicit-exceptions API. | |
// You can find the original package this module was modeled after | |
// at this URL: https://www.npmjs.com/package/explicit-exceptions | |
/** | |
* An exception that can be thrown (or rethrown) within a function decorated with wrap(). | |
*/ | |
class Exception extends Error { | |
/** | |
* @param {string} code - An exception code to distinguish it from other exceptions. (e.g. NotFound) | |
* @param {string} [message] - Optional exception reason, to help with debugging. It's especially useful for when the exception is escalated into a fatal error. | |
* @param {any} data - arbitrary data that you wish to attach to the exception object. | |
*/ | |
constructor(code, message = null, data = null) { | |
super(message ? `${code}: ${message}` : code) | |
this.name = 'Exception' | |
this.code = code | |
this.data = data | |
Object.freeze(this) | |
} | |
} | |
class Ok { | |
constructor(data) { | |
this.data = data | |
} | |
} | |
/** | |
* Unwraps an exception or ok type. If the value is an exception, it will be rethrown, otherwise, it will be returned. | |
* @param {(Exception|Ok)} | |
* @param {string[]} [allowedExceptionTypes=[]] An exception will only be rethrown if it's exception-code is found in this list. Otherwise, it'll be escalated to a fatal error. | |
*/ | |
function unwrap(errOrOk, allowedExceptionTypes = []) { | |
if (errOrOk instanceof Ok) { | |
return errOrOk.data | |
} else if (errOrOk instanceof Exception) { | |
if (exceptionInTypeList(errOrOk, allowedExceptionTypes)) throw errOrOk | |
throw escalatedException(errOrOk) | |
} else { | |
throw new Error('Invalid Parameter') | |
} | |
} | |
/** | |
* Decorates the provided wrappedFn, such that all returned values will be wrapped in an OK instance, and all thrown exceptions will be caught and returned. | |
* unwrap() can be used to retrieve the original returned value, while handling any exceptions that may have occured. | |
* @param {function} wrappedFn | |
* @param {string[]} [allowedExceptionTypes] Optional list of exceptions this function may propagate. Defaults to allowing all. | |
* @returns {(Exception|Ok)} | |
*/ | |
function wrap(wrappedFn, allowedExceptionTypes = null) { | |
if (isAsyncFn(wrappedFn)) { | |
throw new Error('Attempted to call wrap() on an async function. Use wrapAsync() instead.') | |
} | |
return function wrapped(...args) { | |
try { | |
return new Ok(wrappedFn(...args)) | |
} catch (ex) { | |
if (!(ex instanceof Exception)) throw ex | |
if (allowedExceptionTypes == null || exceptionInTypeList(ex, allowedExceptionTypes)) { | |
return ex | |
} | |
throw escalatedException(ex) | |
} | |
} | |
} | |
/** | |
* Same as wrap(), but will correctly wrap an async function. | |
* @returns {Promise} A promise resolving in an Exception or Ok type | |
*/ | |
function wrapAsync(wrappedFn, allowedExceptionTypes = null) { | |
return async function wrapped(...args) { | |
try { | |
return new Ok(await wrappedFn(...args)) | |
} catch (ex) { | |
if (!(ex instanceof Exception)) throw ex | |
if (allowedExceptionTypes == null || exceptionInTypeList(ex, allowedExceptionTypes)) { | |
return ex | |
} | |
throw escalatedException(ex) | |
} | |
} | |
} | |
// This function does a best effort attempt at determining if a function is async. | |
// It will not return true for transpiles async functions or functions that just return promises | |
const isAsyncFn = fn => fn instanceof (async () => {}).constructor | |
const exceptionInTypeList = (ex, types) => types.find(type => ex.code === type) | |
const escalatedException = ex => new Error(ex.message) | |
module.exports = { Exception, unwrap, wrap, wrapAsync } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment