First code-snippet:
console.log("First")
function main() {
var n = 10;
function nested(argument) {
console.log(n);
console.trace();
}
setTimeout(nested, 0);
console.trace();
console.log("Last command inside nested function");
}
main();
console.trace();
console.log("Last");
Here, nested
is being asynchronously called after Last
has been logged.
The console.trace()
command shows that all the functions are in the execution stack, including the function main
, in which nested
is declared.
I was guessing this was because even though the execution context of main
had been popped from the execution stack, nested
still had access to its lexical environment which is why it showed all the functions.
Second code-snippet:
console.log("First")
function main() {
var n = 10;
function nested(argument) {
console.log(n);
console.trace();
}
console.trace();
console.log("Last command inside main function");
return nested;
}
var hello = main();
hello();
console.log("Last");
In this, nested
is being synchronously called, after it is returned from the function main
. In this case, only nested
and the global anonymous function are shown to be in the execution stack.
Why is that? Shouldn't the second one also show all the functions to be in the execution stack?
CodePudding user response:
Shouldn't the second one also show all the functions to be in the execution stack?
No. In the first example, as the trace shows, toplevel called main
, which called setTimeout
, which caused the execution of nested
.
In the second example, toplevel called main
, and then toplevel called hello
. The fact that hello
was constructed inside of main
(and that it closes over some of its variables) has nothing to do with the execution stack.
The reason has nothing to do with synchronous or asynchronous:
function a(val) {
function b() {
console.trace(val);
}
b();
}
const c = a(42); // toplevel calls a, a calls b
function a(val) {
return function b() {
console.trace(val);
}
}
const c = a(42); // toplevel calls a
c(); // toplevel calls b (a.k.a. c)
In both cases, b
closes over val
, and in both cases there is no asynchronicity at all, but the results parallel your examples. This is because stack doesn't care where your values are defined. As far as the execution stack is concerned, it does not matter if a
returns function b
, or the number 42
. The fact that b
remembers val
is similarly irrelevant.
The only thing that the execution stack cares about is, when function b
is executed, which function's execution caused it. In your second example, hello
is the result created by main
, but its execution is caused by toplevel, not main
.
CodePudding user response:
The console.trace() command shows that all the functions are in the execution stack
They're not.
The dev-tools of your browser do keep track of the initial call stack, and then rebuild it for your convenience when you call console.trace()
, or get an Error
instance's .stack
. But the initial function is not on the stack anymore.
Not all browsers do keep that async trace, for instance Safari doesn't and there the trace will start at nested
in your first example. Which is actually correct, but not very useful for debugging.
You can see in your trace that your browser (Chrome) does actually explicitly mark where the "real" stack ended, by marking the previous entry with an (async)
suffix. Firefox has a similar mark in their trace.
It becomes even more mind blowing when you start dealing with async
functions, because after the engine meets an await
clause, everything that happened before, even if it's technically in the same function, is not on the stack anymore. Once again, if you can you may want to check how Safari reports it, since they actually do expose internal methods:
async function foo() {
await null;
console.trace();
}
foo();
Outputs
Trace (anonymous function) (js:3) asyncFunctionResume (anonymous function) promiseReactionJobWithoutPromise
Where you can see that both foo
and the Global Code have been lost.
If you wish, you can read more about this async-trace feature in this V8-dev article: https://docs.google.com/document/d/13Sy_kBIJGP0XT34V1CV3nkWya4TwYx9L3Yv45LdGB6Q/edit
I was guessing this was because even though the execution context of
main
had been popped from the execution stack,nested
still had access to its lexical environment which is why it showed all the functions.
As you may now better understand, that's not the reason, no. It's shown because your browser thinks that if you need the trace it's more because you want to know where the call came from in your code, rather than what was really on the internal call stack. After all the goal is to be useful to the developers.
In this case, only
nested
and the global anonymous function are shown to be in the execution stack.
Because they're only what remains on the call stack. The call to nested
is made from the global (anonymous) context that's being created by the <script>
tag execution, main
has been executed to completion and removed from the stack, everything is as it should be.