'How to change Promise constructor in Node.js project running locally?
This might sound strange, but I would like to alter my local Node.js version and modify the Promise
implementation to add a new source
instance property.
global.Promise = class SourcePromise extends Promise {
constructor(params) {
super(params)
this.source = new Error('This is where this promise was created').stack
}
}
This would be to help me debug an error occurring on my Nuxt app, but only on the server. I'm able to catch the error by listening to the unhandledRejection
event, but the error returned is not an Error
object, it is simply undefined
so I have no clue where it's coming from. The callback of unhandledRejection
also returns the promise so I tried to add the code snippet above at the very beginning of nuxt start
script to be able to log the source like:
process.on('unhandledRejection', (error, promise) => {
console.log('Unhandled Rejection:', error?.stack)
console.log('Promise source:', promise.source)
})
but promise.source
is also undefined. If I log console.log(Promise.resolve().source)
from any script, it works and I get the source so the only explanation I have in mind would be that the promise is created in a child process where my Promise
extension is not defined.
To sum up, since it's happening in a separate process and I can't identify which one, the only way I see of implementing my SourcePromise
globally in all Node processes would be to change the Promise
definition directly in my local version of Node. Is it even possible?
I'm on macOS Monterey 12.3.1 using nvm v0.38.0
EDIT
I ended up cloning Node.js from Github to be able to build it locally and use it to start my Nuxt server. The problem is: it's written in C++ which I don't understand. I think I found where the promise constructor is defined which calls NewJsPromise
that seems to be defined here, but I'll need help from a C++ developer since I still don't know how or where to add the stack...
Solution 1:[1]
(V8 developer here.) Bad news up front: I don't know exactly how to do what you're asking, and I don't have the time to figure it out for you.
But since you're not getting other answers so far, I'll try to say a few things that might help you make progress:
- I don't think this is a "separate process" issue. JS objects, including Promises, can't travel between processes. If you see it in one process, then it was created in that same process.
- There's more than one piece of code in V8 that creates Promises, so to be sure that you'll catch the promise in question, you'll have to update all of them:
NewJSPromise
insrc/builtins/promise-misc.tq
, which you've found, is the Torque (not C++!) implementation, used for both the JavaScriptPromise
constructor and several other builtins. Note that if you put your modifications only into thePromiseConstructor
, that would skip the other uses of the helper, so be sure to updateNewJSPromise
.- There's the C++ version in
Factory::NewJSPromise
insrc/heap/factory.cc
(which is used, probably among other things, for V8's API, i.e. any case where Node itself or a custom Node add-on creates Promises). - There's inlining support in the optimizing compiler in
PromiseBuiltinReducerAssembler::ReducePromiseConstructor
insrc/compiler/js-call-reducer.cc
(which could potentially be dealt with by disabling compiler support for inlining Promise creation).
- The general outline of what you'd do is:
- Update the
Promise
object definition to include another field. - Look at how
Error
objects are created to see how to get the stack.Error
objects employ some fancy "lazy creation" scheme for stack traces for performance reasons; it may be easier to follow that example (to minimize divergence), or it may be easier to simplify it (because you don't care about performance). Note that AFAICTError
objects are always created in C++; you'd have to figure out how to get stacks to Torque-created Promises (off the top of my head I don't have a good suggestion for that). - Update all places where Promises are created (see above) to initialize the new field accordingly.
- Update the
- I strongly suspect that there are less time consuming ways to debug your original problem. (I dunno, maybe just audit all places where you're dealing with promises, and check that they all have rejection handlers? That might take hours, but quite possibly fewer hours than the V8 modification project sketched above.)
Good luck with your investigation!
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | jmrk |