Skip to content

Instantly share code, notes, and snippets.

@pgarciacamou
Last active September 21, 2022 18:14
Show Gist options
  • Save pgarciacamou/88d03714b3c4deccec3396bf43956263 to your computer and use it in GitHub Desktop.
Save pgarciacamou/88d03714b3c4deccec3396bf43956263 to your computer and use it in GitHub Desktop.
Modern JavaScript Decorator Pattern
export class DecoratedError extends Error {
/**
* @param {any} error
* @param {...Object|...() => Object} decorators
*/
constructor(error, ...decorators) {
if (typeof error === "string" || error instanceof String) {
error = new Error(error);
}
// Decorators MUST be idempotent; no mater how many times it is executed
// it must always return the same output, otherwise there is caos.
// new DecoratedError(new DecoratedError(error, decoratorA), decoratorA)
let previouslyParsedData = {};
if (error instanceof DecoratedError) {
previouslyParsedData = error;
error = error.cause;
}
super(error?.message, { cause: error }); // Adds stack trace
this.decorate(defaultDecorator, previouslyParsedData, decorators);
}
/**
* @throws
* @param {...Object|...() => Object} decorators
*/
decorate(...decorators) {
const rawError = this.cause || {};
while (decorators.length > 0) {
const decorator = decorators.shift();
// Functions are higher-order decorators that can return other decorators.
if (decorator instanceof Function) {
decorators.unshift(decorator(rawError, { ...this })); // prepend
continue;
} else if (Array.isArray(decorator)) {
decorators = decorator.concat(decorators); // flatten & prepend
continue;
} else if (decorator === Object(decorator)) {
// Filter out `undefined` properties to prevent unexpected overriding.
for (const key in decorator) {
if (decorator[key] !== undefined && key !== "cause") {
// Override even non-enumerable props
Object.defineProperty(this, key, {
value: decorator[key],
writable: true,
enumerable: true,
configurable: true
});
}
}
}
}
return this; // chain-of-responsibility pattern
}
}
// This decorator could even detect complex structures.
// It doesn't have to be so simple.
function defaultDecorator(rawData, parsedData) {
const { status, code, issue } = rawData;
return { status, code, issue };
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment