Modern JavaScript Decorator Pattern https://pgarciacamou.medium.com/modern-javascript-decorator-pattern-1b440500b38e
Last active
September 21, 2022 18:14
-
-
Save pgarciacamou/88d03714b3c4deccec3396bf43956263 to your computer and use it in GitHub Desktop.
Modern JavaScript Decorator Pattern
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
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