Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save felipeblassioli/885f2eb41a1e88b1b62631157e79945d to your computer and use it in GitHub Desktop.
Save felipeblassioli/885f2eb41a1e88b1b62631157e79945d to your computer and use it in GitHub Desktop.
étude: "decorators" in javascript

Coming from Python I wanted to write something like this:

@trace('name', opts)
def business_logic(foo, bar):
   return 0

To use opentelemetry in specific function and having goodies like putting relevant stuff in the context.

Writting decorators in Javascript in Node.js environment

I am using Node.js 20

Careful with decorator or higher order functions

function example() {}
console.log(example.name); // "example"
const example2 = function() {}
console.log(example2.name); // "example2"
const example3 = () => 0
console.log(example3.name); // "example3"

const exampleFunc = function exampleFunction() {};
console.log(exampleFunc.name); // "exampleFunction"

function traceFn(fn) {
  return function (...args) {
    // Wrapper logic...
    return fn(...args);
  };
}

const tracedExample = traceFn(example);
console.log(tracedExample.name); // undefined

Preserving Function Names and Stack Traces

`trace-fn.js

function example() {}
console.log(example.name); // "example"

function traceFn(fn) {
  const wrapped = function (...args) {
    // Wrapper logic...
    return fn(...args);
  };
  Object.defineProperty(wrapped, 'name', {value: `traced${fn.name}`, configurable: true});
  return wrapped;
}

const tracedExample = traceFn(example);
console.log(tracedExample.name); // "tracedExample"

Error Handling and Stack Traces

etude-trace-fn.js

function traceFn(fn) {
  const wrapped = function (...args) {
    try {
      // Simulate wrapper logic...
      return fn(...args);
    } catch (error) {
      // Create a new error object for clean stack trace handling.
      const tracedError = new Error(error.message);
      
      // Omit this wrapped function from the stack trace to make it cleaner.
      Error.captureStackTrace(tracedError, wrapped);
      
      // Rethrow the new error with the modified stack trace.
      throw tracedError;
    }
  };
  Object.defineProperty(wrapped, 'name', {value: `traced${fn.name}`, configurable: true});
  return wrapped;
}

// Adjust the example function to throw an error.
function example() {
  throw new Error('Something went wrong!');
}

const tracedExample = traceFn(example);

// Execute the wrapped function to trigger the error.
try {
  tracedExample();
} catch (e) {
  console.log(e.stack);
}

Outputs:

Error: Something went wrong!
    at Object.<anonymous> (/tmp/etude-trace-fn.js:30:3)
    at Module._compile (node:internal/modules/cjs/loader:1376:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
    at Module.load (node:internal/modules/cjs/loader:1207:32)
    at Module._load (node:internal/modules/cjs/loader:1023:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:135:12)
    at node:internal/main/run_main_module:28:49

Explanation

  • Inside traceFn, the try-catch block is used to catch any errors thrown by the fn(...args) call.
  • If an error occurs, a new Error object is created (tracedError), inheriting the message from the caught error for clarity.
  • Error.captureStackTrace(tracedError, wrapped); is then called to modify tracedError's stack trace. It removes the wrapped function (and anything above it in the call stack) from the trace. This means when the error is thrown from wrapped, the stack trace will start from where wrapped was called, rather than including the internals of wrapped itself.
  • The modified error is then rethrown, and when caught and logged outside of tracedExample(), it will show a cleaner stack trace that excludes the wrapped function, making it easier to identify the source of the erro
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment