I'm studying about javascript event loop, and I tried some complex and nested async codes, and one of them complicated me so much. The code snippet looks like:
console.log(1);
new Promise((resolve, reject) => {
console.log(2);
resolve();
}).then(() => {
setTimeout(() => { console.log(3) }, 0);
});
setTimeout(() => {
new Promise((resolve, reject) => {
console.log(4);
resolve();
}).then(() => { console.log(5) });
});
And the result sometimes is 1 - 2 - 4 - 5 - 3, and sometimes is 1 - 2 - 4 - 3- 5.
It performs the same in browser environment and node environment.
Maybe my code is written wrong, or there are some issues exist in V8 resolving event loop?
Callback execution order is not really deterministic. Whenever a Promise resolves, a task that executes the .then() chain gets pushed onto the Eventqueue. The same happens when a timer resolves. As timers run concurrently, they might not finish exactly in the order they were started, if they are close to each other.
main code executes (1) {
promise (2) gets pushed onto the queue
promise (4) gets pushed onto the queue
}
promise resolves (2) {
a 0ms timer gets set (3)
}
// maybe the timer is done already (3)
promise resolves(4)
// maybe the timer is done now (3)
You are combining two things: Promise.resolve and window.setTimeout. The former runs synchronously and places the resolved item in the queue immediately. The window.setTimeout however has a different approach. It start to handle the provided function once the timer has expired. When using 0 as delay in window.setTimeout as you have used in your first promise:
setTimeout(() => { console.log(3) }, 0);
then it does not mean "run this immediately". It is more "run this function as soon as possible". That can be changed because the timer is being throttled by browsers. If you do not have problems to read some specs, you can read timer-initialisation-steps to comprehend how it is being initialized in detail.
There is a more easier-to-read information on MDN: Reasons for delays longer than specified
Dependent of the browser/engine, the engine might be busy with (background) tasks so that timers gets throttled. As you have experienced yourself, there is a situation where you are getting different results. The minimum throttling time is (according to the specs, but browsers can use a different value) 4ms. The end result is that the function in the throttled timer gets executed after the another timer that did not got throttled.
Related
This question already has answers here:
Javascript async and await
(3 answers)
Closed 4 months ago.
I'm learning promises and I came across this piece of code
async function main() {
await new Promise((resolve, reject) => {
setTimeout(() => {
console.log("hello");
resolve();
}, 1000);
});
console.log("world");
}
main();
The output this produces is "hello world".
What I'm not able to understand is why the inner console log "console.log("world")" is waiting for the promise to resolve.
Am I missing something here, it would be nice if I can get some help understanding this or maybe some link to some documentation for further reading.
Thanks in advance.
Am I missing something here
Bluntly speaking yes, you're missing first word of the second line.
The key to understand what's happen there is await.
You're creating a promise with
new Promise((resolve, reject) => {
setTimeout(() => {
console.log("hello");
resolve();
}, 1000);
});
This promise will start an asynchronous action, that could end somewhere in the future (after 1 sec in this particular case).
You probably supposed that this action should be executed after console.log("world"); and it could happen if there was no await before promise.
But we do have this await so we litteraly said to program "Hey, I want to wait for this action to end, before I'll go farther".
That's why we see printed hello before world.
Await expressions make promise-returning functions behave as though
they're synchronous by suspending execution until the returned promise
is fulfilled or rejected.
Source
Let's break this down step-by-step without overcomplicating things. This is what is happening inside your main() function:
the first line is executed as expected, but the keyword await inside your function stops the progress (the promise must be first fulfilled or rejected)
inside the promise you have setTimout(), another asynchronous function, but this one is not awaited;
setTimeout() is an asynchronous function, meaning that the timer
function will not pause execution of other functions in the functions
stack. In other words, you cannot use setTimeout() to create a "pause"
before the next function in the function stack fires.
source
Yet, the execution seems to be paused, but it is not due to setTimeout() function on its own. It is because the promise waits to be resolved. And when do we resolve it? After 1 second.
The callback inside setTimeout() is executed as expected: console.log('hello') is printed first and the promise is resolved right after with resolve().
With the promise being resolved, the progress finally continues with the console.log('world').
Thus we end up with
// hello
// world
You can try moving the resolve() outside of the setTimeout() and observe how the promise is resolved right away and the printed message is reversed (because, as I said above, setTimout() does not pause the execution of the promise on its own).
async function main() {
new Promise((resolve, reject) => {
setTimeout(() => {
console.log('hello')
}, 1000)
resolve()
})
console.log('world')
}
// world
// hello
To wrap it up:
What I'm not able to understand is why the inner console log >"console.log("world")" is waiting for the promise to resolve.
The console.log("world") is not waiting for the promise, it is waiting for the delay inside setTimeout(). Just as promise is waiting to be resolved: which happens right after the console.log("world").
Because this is how promises work. They are designed to always return a clear result. Read https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise for more info.
I am not sure how statements in the global scope are placed into the JavaScript event queue. I first thought that the interpreter went through and added all global statements into the event queue line by line, then went and executed each event, but that logic does not line up with the example given below. How does the JavaScript interpreter add global statements to the event queue, and why is the output from the two examples given below different?
let handleResolved = (data) => {
console.log(data);
}
let p = new Promise((resolve, reject) => {
setTimeout(() => {resolve("1")}, 0)
});
p.then(handleResolved);
setTimeout(() => {
console.log("2");
}, 0);
The console output to the above code is
1
2
Now consider this example. Here, the difference is on the body of the promise callback, as there is a nested setTimeout
let handleResolved = (data) => {
console.log(data);
}
let p = new Promise((resolve, reject) => {
setTimeout(() = > {setTimeout(() => {resolve("1")}, 0)}, 0);
});
p.then(handleResolved);
setTimeout(() => {
console.log("2");
}, 0);
The console output to the above code is
2
1
What I don't understand is the order in which things are added to the event queue. The first snippet implies that the promise p will run, and then during its execution, resolve is put in the event queue. Once all of p's stack frames are popped, then resolve is run. After than p.then(...) is run, and finally the last console.log("2");
In the second example, somehow the number 2 is being printed to the console before the number 1. But would things not be added to the event queue in this order
1.) p
2.) setTimeout( () => {resolve("1")}, 0)
3.) resolve("1")
4.) p.then(...)
5.) console.log("2")
I clearly have some sort of event queue logic wrong in my head, but I have been reading everything I can and I am stuck. Any help with this is greatly appreciated.
There are several confusing things in your question that I think show some misconceptions about what is happening so let's cover those initially.
First, "statements" are not ever placed into the event queue. When an asynchronous task finishes running or when it is time for a timer to run, then something is inserted in the event queue. Nothing is in the queue before that. Right after you call setTimeout(), before the time has come for the setTimeout() to fire there is nothing in the event queue.
Instead, setTimeout() runs synchronously, configures a timer in the internals of the JS environment, associates the callback function you passed to setTimeout() to that timer and then immediately returns where JS execution continues on the next line of code. Sometime later when the time has been reached for the timer to fire and control has returned back to the event loop, the event loop will call the callback for that timer. The internals of exactly how this works vary a bit according to which Javascript environment it is, but they all have the same effect relative to other things going on in the JS environment. In nodejs, for example, nothing is ever actually inserted into the event queue itself. Instead, there are phases of the event loop (different things to check to see if there's something to run) and one of the phases is to check to see if the current time is at or after the time that the next timer event is scheduled for (the soonest timer that has been scheduled). In nodejs, timers are stored in a sorted linked list with the soonest timer at the head of the list. The event loop compares the current time with the timer on the timer at the head of the list to see if its time to execute that timer yet or not. If not, it goes about its business looking for other types of events in the various queues. If so, it grabs the callback associated with that timer and calls the callback.
Second, "events" are things that cause callback functions to get called and the code in that callback function is executed.
Calling a function that may then cause something to be inserted into the event queue, either immediately or later (depending upon the function). So, when setTimeout() is executed, it schedules a timer and some time later, it will cause the event loop to call the callback associated with that timer.
Third, there is not just a single event queue for every type of event. There are actually multiple queues and there are rules about what gets to run first if there are multiple different types of things waiting to run. For example, when a promise is resolved or rejected and thus has registered callbacks to call, those promise jobs get to run before timer related callbacks. Promises actually have their own separate queue for resolved or rejected promises waiting to call their appropriate callbacks.
Fourth, setTimeout(), even when given a 0 time, always calls its callback in some future tick of the event loop. It never runs synchronously or immediately. So, the rest of the current thread of Javascript execution always finishes running before a setTimeout() callback ever gets called. Promises also always call .then() or .catch() handlers after the current thread of execution finishes and control returns back to the event loop. Pending promise operations in the event queues always get to run before any pending timer events.
And to confuse things slightly, the Promise executor function (the callback fn you pass as in new Promise(fn)) does run synchronously. The event loop does not participate in running fn there. new Promise() is executed and that promise constructor immediately calls the executor callback function you passed to the promise constructor.
Now, lets look at your first code block:
let handleResolved = (data) => {
console.log(data);
}
let p = new Promise((resolve, reject) => {
setTimeout(() => {resolve("1")}, 0)
});
p.then(handleResolved);
setTimeout(() => {
console.log("2");
}, 0);
In order, here's what this does:
Assign a function to the handleResolved variable.
Call new Promise() which immediately and synchronously runs the promise executor callback you pass to it.
That executor callback, then calls setTimeout(fn, 0) which schedules a timer to run soon.
Assign the result of the new Promise() constructor to the p variable.
Execute p.then(handleResolved) which just registers handleResolved as a callback function for when the promise p is resolved.
Execute the second setTimeout() which schedules a timer to run soon.
Return control back to the event loop.
Shortly after returning control back to the event loop, the first timer you registered fires. Since it has the same execution time as the 2nd one you registered, the two timers will execute in the order they were originally registered. So, the first one calls its callback which calls resolve("1") to cause the promise p to change its state to be resolved. This schedules the .then() handlers for that promise by inserting a "job" into the promise queue.
That job will be run after the current stack frame finishes executing and returns control back to the system.
The call to resolve("1") finishes and control goes back to the event loop.
Because pending promise operations are served before pending timers, handleResolved(1) is called. That functions runs, outputs "1" to the console and then returns control back to the event loop.
The event loop then calls the callback associated with the remaining timer and "2" is output to the console.
What I don't understand is the order in which things are added to the event queue. The first snippet implies that the promise p will run, and then during its execution, resolve is put in the event queue. Once all of p's stack frames are popped, then resolve is run. After than p.then(...) is run, and finally the last console.log("2");
I can't really respond directly to this because this just isn't how things work at all. Promises don't "run". The new Promise() constructor is run. Promises themselves are just notification machines that notify registered listeners about changes in their state. resolve is not put in the event queue. resolve() is a function that gets called and changes the internal state of a promise when it gets called. p doesn't have stack frames. p.then() is run immediately, not later. It's just that all that p.then() does is register a callback so that callback can then be called later. Please see the above 1-11 steps for the sequence of how things work.
In the second example, somehow the number 2 is being printed to the console before the number 1. But would things not be added to the event queue in this order
In the second example, you have three calls to setTimeout() where the third one is nested inside the first one. This is what changes your timing relative to the first code block.
We have mostly the same steps as the first example except that instead of this:
setTimeout(() => {resolve("1")}, 0)
you have this:
setTimeout(() = > {setTimeout(() => {resolve("1")}, 0)}, 0);
This means that the promise constructor is called and this outer timer is set.
then, the rest of the synchronous code runs and the last timer in the code block is then set. Just like in the first code block, this first timer will get to call its callback before the second one. But, this time the first one just calls another setTimeout(fn, 0). Since timer callbacks are always executed in some future tick of the event loop (not immediately, even if the time is set to 0), that means that all the first timer does when it gets a chance to run is schedule another timer. Then, the last timer in the code block gets it's turn to run and you see the 2 in the console. Then, when that's done, the third timer (the one that was nested in the first timer) gets to run and you see the 1 in the console.
If we break down the second case so that each function is on its own we end up with
const handleResolved = (data) => {
console.log(data);
}
const promiseBody = (resolve, reject) => setTimeout( innerPromiseTimeout, 0, resolve );
const innerPromiseTimeout = (resolve) => setTimeout( resolveWith1, 0, resolve );
const resolveWith1 = (resolve) => resolve("1");
const timeoutLog2 = () => {
console.log("2");
};
// beginning of execution
// timers stack: [ ]
// promiseBody is executed synchronously
let p = new Promise( promiseBody );
// timers stack: [ innerPromiseTimeout ]
// this will happen only after resolveWith1 is called
p.then( handleResolved );
// timers stack: [ innerPromiseTimeout ]
setTimeout( timeoutLog2, 0 );
// timers stack: [ innerPromiseTimeout, timeoutLog2 ]
// some time later, innerPromiseTimeout is called
// timers stack: [ timeoutLog2, resolveWith1 ]
// timeoutLog2 is called
// timers stack: [ resolveWith1 ]
// resolveWith1 is called and then is executed in next microtask checkpoint
// timers stack: [ ]
Also note that setTimeout still has a minimum of 1ms in Chrome (they will soon remove it, but for the time being, it's there), so don't assume setTimeout(fn,0) will execute as the next task
function sleep(ms) {
var start = new Date().getTime(),
expire = start + ms;
while (new Date().getTime() < expire) {}
return;
}
async function executeWithDelay(offers) {
return Promise.all(
offers.map((offer, i) =>
getDetailedInfo(offer).then(data => {
offer = data;
if (i % 5 === 0) {
console.log('executed but it delays now for 3 seconds');
sleep(3000);
}
})
)
).then(function(data) {
return offers;
});
}
Trying to achieve web-scraping with possible best solution available. I am combining cheerio and puppeteer together and i have some nice code to debug. The above code works fine if the offers data is less i.e 5-10 records. If it exceeds, the browser gets crashed, though i am running in headless version.
This is basically due to in-house bug from the package what i am using which is unable to handle the load. My experiment is to a little delay for every batch of records and complete the full records execution.
But with the below code, it's behaving as non-blocking code and first time it delays and rest is executed constantly.
Should i use for loop instead of map or is there any alternative to handle this situation?
You never want to "sleep" with a busy-wait loop like that. It means nothing else can be done on the main thread.
Once the operations the promises represent have been started, unless they provide some custom means of pausing them, you can't pause them. Remember, promises don't actually do anything; they provide a standard way to observe something being done.
Another problem with that code is that you're returning offers, not the results of calling getExtendedInfo.
If the problem is calling getExtendedInfo too often, you can insert a delay (using setTimeout, not a busy-loop). For instance, this does the first request to getExtendedInfo almost immediately, the next after one second, the next after two seconds, and so on:
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function executeWithDelay(offers) {
return Promise.all(
offers.map((offer, i) =>
sleep(i * 1000).then(getDetailedInfo)
)
});
}
Obviously, you can adjust that delay as required.
(Note that executeWithDelay isn't declared async; no need, since it has to use Promise.all anyway.)
console.log('1')
setTimeout(() => {
console.log('2')
}, 0)
function three() {
return new Promise(resolve => {
setTimeout(() => {
return new Promise(resolve => resolve('3'))
},0)
})
}
three().then(result => console.log(result))
console.log('4')
This code snippet outputs 1 4 2
This is the behavior I would expect based on my understanding of javascript's event loop and concurrency model. But it leaves me with some lingering questions.
Before getting to those questions, I'll first break down my understanding of this code snippet.
Why code outputs 1
no explanation needed
Why code outputs 4
the callback that outputs 2 gets loaded into the event queue (aka macro task queue) after 0ms, but doesn't get executed until the main call stack is emptied.
even if three was a promise that was immediately resolved, its code is loaded into the job queue (aka microtask queue) and wouldn't be executed until the main call stack is emptied (regardless of the contents of the event queue)
Why code outputs 2
after console.log(4) the main call stack is empty and javascript looks for the next callback to load on the main stack. It's pretty safe to assume that at this point, some "worker thread" had already put the callback function that outputs 2 onto the macro task queue. This gets loaded onto the stack and 2 is output.
Why code does NOT output 3
This is where its a little blurry for me. The function three returns a promise that is then-ed in the main thread. Callback functions passed through then are loaded onto microtask queue and executed before the next task in the macrotask queue. So while you might think it'll run before the callback that logs 2, its actually theoretically impossible for it to run at all. That's because the Promise is only resolved via the callback function of its setTimeout, and that callback function (because of setTimeout) would only run if the main execution thread (the same thread that's waiting for the promise to resolve) is empty.
Why does this bother me
I'm trying to build a complete theoretical mental model of how javascript handles concurrency. One of the missing pieces in that model is the relationship between network requests, promises, and the event loop. Take the above code snippet, and suppose I replace three's setTimeout with some sort of network request (a very common thing in async web development). Assuming that the network request behaves similarly to setTimeout, in that when the "worker thread" is done, a callback is pushed to the macro task queue, it's hard for me to understand how that callback even gets executed. But this is something that happens literally all the time.
Can someone help me understand? Do I have any missing gaps in my current understanding of js concurrency? Have I made an incorrect assumption? Does any of this actually make any sense? lol
Why code does NOT output 3
In this code:
function three() {
return new Promise(resolve => {
setTimeout(() => {
return new Promise(resolve => resolve('3'))
},0)
})
}
three().then(result => console.log(result))
You never resolve the first Promise that three() creates. Since that's the one that is returned form three(), then the .then() handler in three().then(...) is never called.
You do resolve the promise created inside the timer, but you're returning that promise only to the timer callback which does nothing.
If you change your code to this:
function three() {
return new Promise(resolve => {
setTimeout(() => {
resolve('3');
},0)
})
}
three().then(result => console.log(result))
Then, you would see the 3get output.
So, this doesn't have anything to do with the event loop or how it works. It has to do with not resolving the promise that three() returns so the .then() handler on that promise never gets called.
I'm trying to build a complete theoretical mental model of how javascript handles concurrency. One of the missing pieces in that model is the relationship between network requests, promises, and the event loop. Take the above code snippet, and suppose I replace three's setTimeout with some sort of network request (a very common thing in async web development). Assuming that the network request behaves similarly to setTimeout, in that when the "worker thread" is done, a callback is pushed to the macro task queue, it's hard for me to understand how that callback even gets executed. But this is something that happens literally all the time.
Network requests and promises and timers all go through the event loop. There are very complicated rules about how multiple events in queue at the same time are prioritized relative to one another. .then() handlers are generally prioritized first.
Think of the Javascript interpreter as this simplistic sequence.
Get event from event queue
If nothing in the event queue, sleep until something is in the event queue
Run callback function associated with the event you pull from the event queue
Run that callback function until it returns
Note, it may not be completely done with its work because it may
have started other asynchronous operations and set up its own
callbacks or promises for those. But, it has returned from the
original callback that started it
When that callback returns, go back to the first step above and get the next event
Remember that network requests, promises, timers and literally ALL asynchronous operations in node.js go through the event queue in this manner.
Why you assume that network request would behave as setTimeout?
It is a Promise which resolved() would go in microtasks
This question already has answers here:
Promise.resolve().then vs setImmediate vs nextTick
(3 answers)
Closed 4 years ago.
I have an unsolved JS behavior that I cannot understand.
I'm running this code on node v8.4.0.
I'm running this code twice.
First time with f1()
second time with f2()
f2() the result is as expected. 'start' is printed first and then 'end'.
f1() the result is not as expected. 'end' is printed first and then 'start'.
Can someone please explain to me the result of the code below?
const fs = require('fs')
function f1() { return new Promise((resolve, reject) => { resolve() }) }
function f2() {
return new Promise((resolve, reject) => {
fs.readFile('/Users/adi/Downloads/profile.jpg', resolve)
})
}
async function main() {
setImmediate(() => { console.log('start') })
await f1()
console.log('end')
}
main()
//f1 output:
end
start
//f2 output:
start
end
As far as I know, the result should be 'start' and then 'end'.
What am I missing?
The queue with the resolved Promises will be check before the queue with setImmediate(() => { console.log('start') })
Because the f1 resolves immediately, both the callback of the setImmediate and the resolved Promise are added to the event queue at the same time, but at different stages. Resolved Promises have a higher priority then callbacks added with setImmediate
If you use process.nextTick then the callback will be added with an higher priority then setImmediate and start will be logged before end
function f1() { return new Promise((resolve, reject) => { resolve() }) }
async function main() {
process.nextTick(() => { console.log('start') })
setImmediate(() => { console.log('start') })
await f1()
console.log('end')
}
main()
For f2 the reading of the file will involve a longer lasting async task so the setImmediat will be still called before.
So, in your f1() example, you have a race between setImmediate() and the .then() handler of an immediately resolved promise since both will be in the event queue at the time the next event is ready to be processed.
When both are ready to run, one runs before the other because of the internals of how different async things like setImmediate() and promises are coded to work in the node.js implementation of its event loop. Internal to the event loop in node.js, there is a sequence or priority for some different types of asynchronous operations and some go before others if all are waiting to go. It is possible, though difficult, to fully understand which goes before the others, but it is very complicated and it is mostly an implementation detail, not something fully documented by specification.
In this specific case, native promises in node.js use a microTasks queue (there are apparently a couple separate microTasks queues) and they are run before things like setImmediate(), timers and I/O events.
But, in general, it is best to not rely on fully understanding all that and, if you want one thing to happen before the other, don't allow it to be a race between the two inside of node.js. Just code it with your own code to force the sequence you want. This also makes your code more obvious and declarative what order you expect things to be processed in.
If I read your current code, I would think that you purposely set up a race between f1() and setImmediate() and did not care which one ran first because the code is not declarative and does not define a desired order.
For more info on the details of the internals of different types of async operations in the event loop, you can read these references:
Promise.resolve().then vs setImmediate vs nextTick
Promises, Next-Ticks and Immediates— NodeJS Event Loop Part 3
Promises wiggle their way between nextTick and setImmediate
Here's a quote from this last reference article:
Native promise handlers are executed on a microtask queue which is roughly the same as nextTick, so they run before everything else. Pure javascript [promise] implementations should use nextTick for scheduling.
For your f2() example, it's probably just that fs.readFile() takes some finite amount of time so f2() does not resolve immediately and thus isn't ready to run at the same time that setImmediate() is so the setImmediate() runs before f2() resolves.
It's works like this, because Promise is a microtask. Microtasks are executed at the end of call stack, before macrotasks. You can read more here