in this diagram is says that the event Loop will run the I/O callbacks (the axios request), then the check phase (setImmediate), but when i tested this, it was the opposite, i need explanation of the execution of that code.
console.log('first'); // logs instantly
setImmediate(() => console.log('immediate')); // will be executed in the next iteration of the event loop.
new Promise((resolve) => { // starts in promise in pending state
console.log('insidePromise');
const data = 'Promise';
resolve(data);
}).then((d) => console.log(d)); // prints after Promise is resolved
Output -
first
insidePromise
Promise
immediate
When the Promise is called it starts in a pending state and waiting for resolve.
setImmediate - triggers here before resolve is processed as setImmediate will have to be executed in the event loop.
https://nodejs.dev/learn/understanding-javascript-promises
Once a promise has been called, it will start in a pending state. This means that the calling function continues executing, while the promise is pending until it resolves, giving the calling function whatever data was being requested.
https://nodejs.dev/learn/understanding-setimmediate
Any function passed as the setImmediate() argument is a callback that's executed in the next iteration of the event loop.
Related
I can’t figure out how the code below works, my example of work contradicts the execution of the event loop.
async function main() {
for (let i = 0; i < 10; i++) {
await new Promise(res => setTimeout(res, 1000));
console.log("time + i");
}
}
main();
My example of how this code works:
function main added to Call stack.
call the loop for.
call function Promise which return setTimeout.
setTimeout added to Macrotask.
function main add to Microtask (await resolve Promise).
Some remarks about the steps you listed:
call function Promise which return setTimeout.
Promise is called as a function, but more specifically as a constructor
It doesn't return setTimeout, but ... a promise.
setTimeout added to Macrotask.
setTimeout is not added to a queue. setTimeout is executed, with res and a delay as argument. It returns immediately. The host has registered the callback and timeout using non-JavaScript technology. There is nothing about this in the queues yet.
function main add to Microtask
More precisely, the current execution context of main is saved. Once the awaited promise has resolved, only then will a job be put in the microtask queue with the purpose to resume the suspended execution of main.
Detailed steps
Here are the steps 3, 4 and 5 in more detail:
The Promise constructor is called, which immediately calls the callback given as argument.
setTimeout is called, which registers its callback argument res for later execution.
The setTimeout call immediately returns. Its return value is a unique timer ID, but that value is ignored by the Promise constructor
The Promise constructor returns a promise that is in a pending state.
The await saves the current execution context of main, and makes the main function return with a pending promise.
This is the end of this synchronous execution cycle, and JavaScript now monitors the job queues.
In a following step we have this:
The setTimeout implementation (non-JavaScript) sees that the 1000 delay has passed and places a job in a (macrotask) job queue.
The JavaScript engine gets the job from that queue, and executes it. In this case it means it executes res (starting from an empty call stack).
The call of res will fulfill the promise that was created with new Promise. This creates a job in a (microtaks) job queue to restore the execution context of main.
res returns and the callstack is empty again.
And then, as part of the same "task":
JavaScript reads the microtask queue and executes that job: the main execution context is restored (put on the callstack), with variable i equal to 0 and execution continuing with console.log
The loop makes its second iteration, and the operand of await is evaluated again, calling again the Promise constructor (see the steps at the top). The difference here is that when await makes main return (again) it is returning from a "resume" call that was made from a job, not from JavaScript code. This will be the case for all next executions of await.
And so it continues for all iterations of the (asynchronous) loop. When the loop has completed:
The resumed main function ends with an implicit return undefined. This resolves the promise that main had returned when it had executed await for the first time -- when it returned to the top-level script where main(); had been called.
As there is no code that is awaiting that promise to be resolved, nothing more happens.
In the following code how does JavaScript determine that the state of myPromise has become "fulfilled"? I.e., how is the determination made that it's time to put the .then() handler into the microqueue for subsequent execution?
const myPromise = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('Resolved promise: ');
}, 2000);
});
myPromise.then((resolvedValue) => {
console.log(resolvedValue + 'The .then() handler is now running');
});
// Output (after ~2 seconds): "Resolved promise: The .then() handler is now running"
Answer
You calling the function resolve changes the state of the promise, thus JS can know that the promise's state has changed by you calling resolve.
The callback attached to that promise with .then() will then be known to be scheduled (as a microtask).
Explaining your code
You instantiate a new Promise and provide a callback that is run immediately. The callback schedules a task to run after 2000ms. That task will resolve the promise upon execution.
After having constructed the promise, you then attach a callback via .then() to the returned promise. This will only execute once the promise has fulfilled.
Now your synchronous code has run to completion, so the event loop has time to execute another task. The next task may be the one after 2000ms, which resolves the promise (and therefore sets its state to "fulfilled").
Once the task to resolve the promise has finished executing, a microtask will be scheduled to run immediately after: That microtask will run the .then() callback. This will finally log your string.
Why is there no async stacktrace when rethrowing an asynchronous exception? With node 12+, the exception when running the following code:
async function crash() {
try {
await (async () => {throw new Error('dead');})();
} catch (e) {
throw new Error('rethrow');
}
}
async function foo() {
await new Promise(resolve => setTimeout(() => resolve(), 1));
await crash();
}
async function entrypoint() {
try {
await foo();
} catch(e) {
console.log(e.stack);
}
}
entrypoint();
is woefully incomplete:
Error: rethrow
at crash (/async-stackt/crash.js:6:15)
I found a workaround by defining the exception in the beginning of crash(), which yields a much nicer:
Error: rethrow
at crash (/workaround.js:2:17)
at foo (/workaround.js:12:11)
at async entrypoint (/workaround.js:17:9)
This is not optimal, since the error has to be constructed in advance whether it is needed or not, and the stacktrace is somewhat imprecise.
Why is the stack trace incomplete when throwing an error from an async catch block? Is there any workaround or change to the code to get a complete stack trace in the first place?
That stack trace is showing you what's ON the stack at that time. When you make an asynchronous call such as setTimeout(), it runs the setTimeout() which registers a timer that will fire some time in the future and then it continues execution. Since you're using await here, it pauses the execution of foo(), but it continues execution of the code after where foo() was called. Since that's also an await, it continues executing the code that called entrypoint(). After that finishes, the stack is entirely empty.
Then, sometime later, your timer fires and its callback gets called with a completely clean stack. In your case, the setTimeout() callback just calls resolve() which then triggers a promise to schedule its resolve handlers to run on the next event loop tick. That returns back to the system and the stack frame is again empty. On that next tick of the event loop, the promise resolve handlers are called and that satisfies the await on that promise which is inside a function context. When that await is satisfied, the rest of that function starts to execute.
When that function gets to the end of its execution, the interpreter knows that this was a suspended function context. There is no return from the function to happen because that already happened earlier. Instead, since this is an async function, the end of the function execution resolves the promise that this async function returns. Resolving that promise then schedules its resolve handlers to be called on the next tick of the event loop and then it returns control back to the system. The stack frame is again empty. On that next tick of the event loop, it calls the resolve handlers which satisifies the await in the await foo() statement and the entrypoint() function can continue to run, picking up where it was last suspended.
So, the key here is that when the timer goes off, execution goes from foo back to entrypoint, not via a stack and a return statement (that function already returned awhile ago), but via promises getting resolved. So, at the time the timer goes off and you then call crash(), the stack is indeed empty except for the function call to crash() itself.
This concept of an empty stack when promises are resolved goes to the heart of how an async function actually works so it's important to understand that. You have to remember that it pauses the internal execution of the function containing the await, but as soon as it hits the first await, it immediately causes the function to return a promise and the the callers continue further execution. The caller is not paused unless they also so an await in which case the callers of the caller continuing executing. At some point, somebody gets to continue executing and eventually, it returns control back to the system with a now-empty stack.
The timer event (or some other promise triggering event) then gets called with a completely empty stack frame with no remnants from the original call sequence.
Unfortunately, the only work-around I'm aware of now is to do what you found - create the Error object earlier when the original stack is still alive. If I remember correctly, there is a discussion about adding some features to the Javascript language to make asynchronous tracing easier. I don't remember the details of the proposal, but perhaps by remembering what the stack frame was when the function was originally called since what it is after the promise is resolved/rejected and when the Error object is created is no longer very useful.
In case anyone was unfamiliar with how async functions work and how they suspend their own execution upon the first await, but then return early, here's a little demo:
function delay(t) {
return new Promise(resolve => {
setTimeout(resolve, t);
});
}
async function stepA() {
console.log("5");
await stepB();
console.log("6");
}
async function stepB() {
console.log("3");
await delay(50);
console.log("4");
}
console.log("1");
stepA();
console.log("2");
This generates the following output. If you follow this execution path step-by-step, you will see how each await causes an early return from that function and can then see how the stack frame will be empty once the promise that is being awaited gets resolved. This is the output generated:
1
5
3
2
4
6
It's clear why 1 is first as it's the first thing to execute.
Then, it should be clear why 5 comes next when stepA() is first called.
Then, stepA calls stepB() so as it begins to execute, that's why we see 3 next.
Then, stepB calls await delay(50). That executes delay(50) which starts a timer and then immediately returns a promise that is hooked to that timer. It then hits the await and it stops execution of stepB.
When stepB hit that await, it causes stepB at that point to return a promise that comes from the function being async. That promise will be hooked to stepB execution eventually (in the future) getting a chance to finish all of its execution. For now the execution of stepB is suspended.
When stepB returns its promise, that goes back to where stepA executed await stepB();. Now that stepB() has returned (an unfulfilled promise), then stepA hits its await on that unfulfilled promise. That suspends the execution of stepA and it returns a promise at that point.
So, now that the original function call to stepA() has returned (an unfulfilled promise) and there is no await on that function call, that top level code after that function call continues to execute and we see the console output the 2.
That console.log("2") is that last statement to execute here so control is returned back to the interpreter. At this point, the stack frame is completely empty.
Then, sometime later, the timer fires. That inserts an event in the JS event queue. When the JS interpreter is free, it picks up that event and calls the timer callback associated with that event. This does only one thing (call resolve() on a promise) and then returns. Calling resolve on that promise schedules that promise to trigger it's .then() handlers on the next tick of the event loop. When that happens, the await on the line of code await delay(50); gets satisfied and execution of that function resumes. We then see the 4 in the console as the last line of stepB executes.
After the console.log("4"); executes, stepB has now finished executing and it can resolve it's async promise (the one returned by it earlier). Resolving that promise tells it to schedule its .then() handlers for the next tick of the event loop. Control goes back to the JS interpreter.
On the next tick of the event loop, the .then() handlers notify the await in the await stepB(); that the promise has now been resolved and execution of stepA continues and now we see 6 in the console. That is the last line of stepA to execute to it can resolve its async promise and return control back to the system.
As it turns out, there is nobody listening to the async promise that the call to stepA() returned so there is no further execution.
I ran into the same issue in Node 12 and found out that it was fixed in a later version of V8: commit.
Gotta wait for Node 14, I guess...
The bug had to do with the stack trace not being tracked in catch blocks. So one workaround is to use the Promise's .catch function instead:
async function crash() {
await (async () => {throw new Error('dead');})()
.catch(e => { throw new Error('rethrow'); });
}
I'm trying to figure out how promises are handled in the runtime environment. Are they moved into the web API container until they resolve and then pushed into the callstack when .then is called? Here is some example code. Console.log runs before the promises which leads me to believe somewhere along the way they end up in the queue. I also noticed I can put a function in a .then and the returned promise will fill that functions parameters.
// asynchronous test
let promiseWhatever = new Promise( function(resolve, reject){
// variable to be chained later and passed in function argument
let chainedVariable = 'I am chained';
resolve(chainedVariable);
reject('rejected promise');
});
let promiseMe = function(promiseResult) {
let message = `${promiseResult} to my computer`;
return Promise.resolve(message);
// resolves here to be passed onto the second chained then
};
function hello() {
promiseWhatever
.then(promiseMe)
// how does promiseMe take in the result for its argument?
// then returns another promise and you can chain them
.then( function(fulfilled){
console.log(fulfilled);
}) // is fullfilling the code to display the string to the console.
.catch( function(err) {
console.log(err);
});
console.log('hello'); // logs first to the console
};
hello();
First off a promise is just a notification scheme. The underlying asynchronous operation (whatever code resolves or rejects the promise) that would typically be outside of Javascript (using native code) such as an incoming webSocket message or an ajax response or something like that.
All promise engines use the event queue. When a promise is resolved, they post an event to the event queue in order to trigger the appropriate .then() or .catch() handler to be called. It is not required by the language or promise specification, but a number of implementations use a special event queue for promise callbacks that is checked along with other types of event queues.
It is required by the promise specification that a .then() or .catch() handler is always called asynchronously AFTER the current event loop code has finished even if the promise is resolved immediately. That's why your console.log('hello') shows before the console.log() inside the .then() handler. This is by design and is done in order to create consistency on when a promise calls it's handlers (always after the current event loop code completes).
Are they moved into the web API container until they resolve and then pushed into the callstack when .then is called?
When a promise is resolved an event is inserted into the event queue which will cause the appropriate .then() callbacks to get called after the current event loop code has completed (on a future event loop cycle).
It's not clear what you mean by "web API container" so I can't comment on that.
I also noticed I can put a function in a .then and the returned promise will fill that functions parameters
Yes, this is how promises work. A .then() handler is passed a single argument that represents the resolved value of the promise. A .catch() handler is passed a single argument that represents the reject reason.
The Promise.all MDN docs contain an example of evaluating multiple Promise.all results, but within a setTimeout function without a timeout value.
From the docs:
// this will be counted as if the iterable passed is empty, so it gets fulfilled
var p = Promise.all([1,2,3]);
// this will be counted as if the iterable passed contains only the resolved promise with value "444", so it gets fulfilled
var p2 = Promise.all([1,2,3, Promise.resolve(444)]);
// this will be counted as if the iterable passed contains only the rejected promise with value "555", so it gets rejected
var p3 = Promise.all([1,2,3, Promise.reject(555)]);
// using setTimeout we can execute code after the stack is empty
setTimeout(function() {
console.log(p);
console.log(p2);
console.log(p3);
});
// logs
// Promise { <state>: "fulfilled", <value>: Array[3] }
// Promise { <state>: "fulfilled", <value>: Array[4] }
// Promise { <state>: "rejected", <reason>: 555 }
Can someone help explain what this achieves, with a few more words than the comment in the code?
setTimeout called with no delay value just puts the passed in function on the queue to be executed by the JavaScript event loop. That is usually the next tick, although there could be other things on the queue already scheduled, so the function will be after those.
Promise resolution gets scheduled similarly by putting it on the queue. That means setTimeout will schedule the function completion immediately after the promises are finalized. In turn, this means that the value of p, p2, and p3 will be pending in the current run of the JavaScript event loop, then in the final state when the function delayed by setTimeout is called.
Demonstrating the flow of the program:
//console.logs from StackOverflow snippets don't print the state of the promise
//you can open the browser developer tools and click the button to run the snippet
//in order to see that output
var p = Promise.all([1,2,3]); //<-- a pending promise is created
p.finally(() => console.log("promise fulfilled", p));
console.log("this call is executed immediately", p); //<-- the same run of the event loop
setTimeout(function() {
console.log("this call is scheduled to execute in the future", p); //<-- to be executed in a subsequent run of the loop.
});
In JavaScript promises have a higher priority than timeouts so in theory they should all be fulfilled and ready to be logged by the time the function in the timeout is executed.