3

Here's the middleware that I use in express:


    const app = express();
    const port = 8000;
    const f = () => {
        return async (req, res, next) => {
            await new Promise(resolve => setTimeout(resolve, 3000));
            return next();
        }
    }
    const namedFunction = f();
    app.use(namedFunction); // earlier I was using `app.use(f());` 

But my function still appear as anonymous function in profiler: Something like this:

enter image description here

A bit of background: We want to see which middleware is causing the high latency, but because the middlewares are anonymous, we can't narrow down the cause in our APM + JS profiler. The preceding is just one example; we use approximately 40 middleware packages over which we have no control.

That's why I thought passing f() to namedFunction should fix the issue but it wasn't so looking for help on this.

Other tries till now: As per Jonsharpe's comment I tried:

app.use(function namedFunction() { f()(...arguments) });

But in profiler it still appear as an anonymous function

6
  • could be the callback function youre returning Commented Aug 2, 2022 at 13:49
  • 6
    If you want it to have a name why not make it not an anonymous function? Write a vanilla function (return async function <name>() { ...) rather than an arrow function. Commented Aug 2, 2022 at 13:52
  • Updated my Question @jonrsharpe hope my ask is clear now. I don't have a control over f() content so that's why tried using namedFunction but it didn't work Commented Aug 2, 2022 at 13:57
  • Write a proxy function, maybe: app.use(function namedFunction() { f()(...arguments) });? Or rename it as shown in stackoverflow.com/q/5871040/3001761. Commented Aug 2, 2022 at 13:59
  • Giving it a try, probably should work but could JS profiler still takes up f()(...arguments) as an anonymous function Commented Aug 2, 2022 at 14:00

3 Answers 3

2

While I do not know much about express, I can at least clear up misunderstandings about anonymous functions.

When you create and immediately assign a function to a variable (or to the property of an object), the interpreter can implicitly use the name of the variable (or property) as the name of the function. That is true of functions created with the arrow notation, and those created with a function expression without a name.

const square = x => x**2;
square.name; // "square";

const xmath = {
    square: function (x) {
        return x**2;
    },
};
xmath.square.name; // "square"

But if you create a function without an explicit name and immediately pass it as an argument or return it, the interpreter cannot deduce a name for it. That is what we call an anonymous function. Even if you immediately assign the function to a variable after it was returned, it is too late.

So, given a function f() which returns an anonymous function, this code...

const namedFunction = f();

...only assigns that anonymous function to a variable, but doesn't give it a name. If you want namedFunction to really hold a named function, you need to wrap the anonymous function in a new one, like so:

const unnamedFunction = f();
const namedFunction = (...args) => unnamedFunction(...args);

Since this wrapper function is created and immediately assigned to namedFunction, it will be given the name namedFunction implicitly.

You could avoid creating an anonymous function in the first place by assigning an arrow function to a variable before returning it.

const meaningfulName = () => { ... };
return meaningfulName;

But when I'm not relying on the other behaviors of arrow functions, I prefer using a named function expression instead:

return function meaningfulName () { ... };
Sign up to request clarification or add additional context in comments.

1 Comment

A very good explanation of why this happens, which is, IMO, often as important as the answer itself.
1

In this answer, an example is shown that redefines the name property of a function which is normally read-only. This seems to work just fine in v8.

// Apart from the added error so we can log a stack trace,
// this is unchanged from your example:
    const f = () => {
        return async (req, res, next) => {
            throw new Error("Intentionally cause an error to log the function's name.");
            await new Promise(resolve => setTimeout(resolve, 3000));
            return next();
        }
    }
    const namedFunction = f();

When the function is called and its error is logged, you'd get a stack trace like this, as you saw in your profiler, the function has no name:

namedFunction().catch(console.log);

// Error: Intentionally cause an error to log the function's name.
//     at /tmp/namedfn.js:3:19
//     at Object.<anonymous> (/tmp/namedfn.js:9:5)

Rename as per the linked answer:

Object.defineProperty(namedFunction, 'name', {value: 'someFn'});
namedFunction().catch(console.log);

// Error: Intentionally cause an error to log the function's name.
//     at someFn (/tmp/namedfn.js:3:19)
//     at /tmp/namedfn.js:14:9

It is now named 'someFn' and should show up in your profiler as such.


Note that this answer is better / less hacky in cases where the source can be edited (OP doesn't have control over f()'s content according to a comment).

1 Comment

Didn't work you it still marks it as anonymous since in the end it's running in the context of the anonymous function
1

After a lot many tries of assigning name, refactoring use I came up with this and finally the profiler was able to point out that it's the wrappedFunction which is causing it so going ahead I'll need to create a wrapperFunction for each of the case.

Here's a sample of what worked in the end:

const f = () => {
        return async (req, res, next) => {
            await new Promise(resolve => setTimeout(resolve, 3000));
            return next();
        }
    }
    const wrappedFunction  = async(req, res, next) => {
        await new Promise(resolve => f()(req, res, resolve)); // Now since time is spent in this block that's why profiler is picking this up instead of the anonymous function as the main resource consuming function
        next();
    }
    
    app.use(wrappedFunction);

And here's what it looks like in profiler now:

enter image description here

Just an note to others who might not know the context: By default the official middlewares are usually named functions but some 3rd party middleware return an anonymous function which profiler/APM isn't able to pick up and point to code block from there. That's why it's important to have a named function instead of anonymous middleware showing up in UI and being unclear where to look at.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.