let arr = [2, 3]
let promiseA = new Promise((resolve)=>{
// await for io
resolve(arr.shift())
})
let promiseB = new Promise((resolve)=>{
// await for io
resolve(arr.shift())
})
let handler = (data)=>{
if(!data){
return console.log("no more")
}
console.log(data)
}
promiseA.then(handler)
promiseB.then(handler)
Hello, I have a question about concurrence in javascript.
If promiseA and promiseB will be resolved one by one, there will be no problem then.
But, if the the two io operations take the same time, is there a chance that both promiseA and promiseB will be resolved with the value 2?
#Pointy (in the comments) is correct, but just to formalize it:
Promise body, messages, and the event loop
Any code that runs in the body of the two Promises will execute as messages in the main JavaScript Event Loop.
JavaScript runs one message at a time and once a message is running in the main event loop it will run to completion before anything else happens:
...it cannot be pre-empted and will run entirely before any other code runs (and can modify data the function manipulates).
Calls like setTimeout queue messages for the main event loop, so the work done in the body of the two Promises may span multiple messages, but each message is run one at a time and run to completion before anything else runs.
So at the time that each promise body reaches this line:
resolve(arr.shift())
...it will be the currently running message and "cannot be pre-empted and will run entirely before any other code runs".
Promise callbacks and the PromiseJobs queue
When a Promise resolves, any callbacks waiting on the Promise get queued in the PromiseJobs queue which is a special Job Queue introduced with ES6.
Callbacks in the PromiseJobs queue run after the current message completes and before the next message begins and run one at a time, in order, until the queue is empty.
So...is there a chance that both promiseA and promiseB will be resolved with the value 2?
No. There is a zero chance that both Promises will resolve with the value 2.
This line:
resolve(arr.shift())
...is synchronous and the array operation and resolve will both run to completion as part of the currently running message before any other code runs.
This means that each handler callback is guaranteed to be queued in PromiseJobs with a unique value.
Bonus
The way the code is written, handler is guaranteed to be queued in PromiseJobs with 2 first, and then to be queued in PromiseJobs with 3 afterwards, regardless of which Promise resolves first, so the handler callbacks are guaranteed to run in that order and the above code will always print 2 before it prints 3.
Related
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
The actual output (when seen in Firefox console) of the below code is
- error1
- error2
- continue1
- continue3.
Promise
.resolve()
.then(() => Promise.reject('error1'))
.catch(console.log)
.then(() => console.log('continue1'))
Promise
.resolve()
.then(() => Promise.reject('error2'))
.then(() => console.log('continue2'), console.log)
.then(() => console.log('continue3'))
As per my understanding of promises, it should be - error1 - continue1 - error2 - continue3.
Need help in understanding the actual output
First off, it is kind of pointless to attempt to understand a possible ordering issue between two completely independent promise chains. When programming, you should treat their order as indeterminate (and in fact, as soon as you have real asynchronous operations inside the promise chains which any real promise chain implementations would usually contain, the order will become indeterminate). If the order matters to you between the two chains, then you need to add some specific code to coordinate the actions of the two independent promise chains.
So, the observed order in your snippet is this:
error1
error2
continue1
continue3
Since you have no async operations in any of your .then() or .catch() handlers, it will simply alternate between the two promise chains (which is what it appears to be doing when you run your snippet). Here's how it would basically execute:
First Promise.resolve() executes which adds to the event queue an event to run its .then() handlers.
Second Promise.resolve() executes which adds to the event queue an event to run its .then() handlers.
Your sequence of JS is done so control returns back to the event queue. First .then() handler is called which then inserts another event into the event queue to run the .catch() handler and returns control back to the event loop.
Second .then() handler is at the top of the event queue and gets to run
And so on...
Keep in mind that per the promise specification each successive .then() or .catch() handler must be run asynchronously (on the next turn of the event queue). The promise chain doesn't execute as far as it can in one fell swoop. It executes the next step, puts the step after that into the queue and then returns control to the event loop which pops the next event off the queue. This will cause control to rotate between different promise chains that aren't waiting for something else to complete.
But, I repeat, you should not be depending upon this level of execution order and, in more complicated real-world cases with other async operations involved, you could not event attempt to predict an order. You should assume that these two separate promise chains run in an indeterminate order beyond the synchronous part (each Promise.resolve()). And, then if any specific ordering matters to your code, you must add code to actually synchronize and control the execution order using Promise.all() or linking the chains together into one branched chain or one sequence chain or something like that depending upon the needs of the code.
Promises that resolve/reject put a microtask in the corresponding queue. That task involves calling any then/catch callback functions. The messages in that queue are processed in sequence.
So in your example the sequence is:
First Promise.resolve() is executed. The microtask queue now has 1 entry:
Queue = [promise #1 resolved]
Normal execution flow continues to the second Promise.resolve()
Second Promise.resolve() is executed. The microtask queue now has 2 entries:
Queue = [promise #1 resolved, promise #2 resolved]
Normal execution flow ends -- the call stack is empty.
The first item in the queue is extracted and processed. The then callback () => Promise.reject('error1') is executed. This creates a new, rejected promise, and so a rejection is put on the queue
Queue = [promise #2 resolved, promise.then #1 rejected]
The first item in the queue is extracted and processed. The then callback () => Promise.reject('error2') is executed. This creates a new, rejected promise, and so a rejection is put on the queue
Queue = [promise.then #1 rejected, promise.then #2 rejected]
The first item in the queue is extracted and processed. It is a rejection, so the catch callback console.log is executed. This outputs "error1" and creates a new, resolved promise (with undefined value). The queue
Queue = [promise.then #2 rejected, promise.then.catch #1 resolved]
... etc
I am not completely sure the exact execution order of unrelated promises is specified anywhere, and even if it is, you probably want to introduce some explicit ordering dependency between the two promises if this is important to you.
But here is one possible and likely sequence:
Step one: Both promises are already resolved
Step two: Being resolved, both promises run their first then. This results in both being rejected. I would not depend on the order in which these two execute with respect to each-other.
Step three: Being rejected, both promises run their second catch/then. I would not depend on the order in which these two execute with respect to each-other.
You can probably depend on these steps going in that order (because it follows the queuing order of how these callbacks were submitted to the scheduler), but even that seems risky.
If you want to have the continue1 happen before the error2 you have to arrange code that waits for completion of the former.
I'm still trying to figure out the event loop and when exactly things tick/when the task queues are processed.
console.log('start');
Promise.resolve().then(function() {
console.log('promise');
});
console.log('end');
This outputs the following:
> "start"
> "end"
> "promise"
Shouldn't the promise resolve right away and thus the call stack would be empty right after (before "end" is printed) and thus the callback would be processed as a task before "end" is printed?
I'm clearly missing something here about either a) when resolved Promise callbacks are added to the task queue or b) when tasks are processed in the event loop.
Using the terminology you used. Task queues are executed when only platform code is running - that is when all synchronous code has finished executing. Your code is guaranteed to execute in exactly that order.
They are run before the event loop (they're "microtasks") which is why your then executes before any setTimeout for example.
The problem was that I was considering the stack to be clear after the immediately resolved Promise, but in reality the stack is not clear at that point. You could think of the entire block of code being executed as like the main() function of a C program to make a C analogy.
Thus, since the stack is not clear, even though an microtask in now in the callback queue waiting to be processed, it doesn't get processed until after all the synchronous JS code in the main thread is completed.
When I have the following code:
var promise1 = Promise.resolve([1, 2, 3]);
promise1.then((value) => {
console.log(value);
// expected output: Array [1, 2, 3]
});
console.log('end of script');
I know that end of script is returned earlier because the promise is asynchronous. But at what point of execution does it become asynchronous?
Is Promise.resolve() asynchronous?
Or is .then asynchronous or even both functions?
Is there some other mechanism playing under the hood?
(was a hell to google because I get only results of the new async await features)
Promise.resolve([1, 2, 3]);
Is Promise.resolve() asynchronous?
No that's just a regular function call. It will return a result immediately.
Or is .then asynchronous or even both functions
No. Both aren't asynchronous in the sense that
promise1.then((value) => console.log(value));
will immediately return a new Promise for chaining.
However a Promise definitely resolves asynchronously, which means that the inner function (value => console.log(value)) is called definitely after your whole synchronous code executed.
Is there some other mechanism playing under the hood?
Yes there is a "magic" event loop in the background which manages all the async events.
All answers are nice but i would like to mention a little subtle point here. In JS not all asynchronous animals are equal. Promises use microtask queue while the oldschool async callbacks use the event queue.
So as for below code please try to guess the order of logs before you run it..
console.log("Code starts here");
setTimeout(console.log,0,"setTimeout resolved");
Promise.resolve("promise resolved")
.then(s => (console.log(s), "yet another promise resolved"))
.then(s => console.log(s));
console.log("Code ends here")
So as you see microtask queue which is used by Promises are privileged to the event queue and when it's time comes the instruction at the head of the microtask queue takes precedence compared to the poor one at the head of the event queue even though they both resolve at the same time just like above.
Here is the exact order of what goes on. Before we begin, you should note that Promise objects are always in one of three states:
Waiting for evaluation to complete.
Resolved with a final value.
Rejected with a "reason" value.
First, promise1 is assigned a promise that is in the "Resolved" state with a final value of [1,2,3]:
var promise1 = Promise.resolve([1, 2, 3]);
Next, we ask that, once promise1 enters its resolved state, we log its final value. However, .then(), .catch(), etc. in promises are never evaluated until the whole program is passed through and execution enters the Javascript event loop. Thus, this chunk of code registers the future action that is to be completed but does not actually complete it; this chunk of code returns right away without completion occurring.
promise1.then((value) => {
console.log(value);
// expected output: Array [1, 2, 3]
});
After that, we print some text:
console.log('end of script');
At this point, Javascript's execution returns to its event loop, it finds the .then() that we registered earlier, and it carries out the relevant function ((value) => console.log(value)).
There is a helpful article describing how the event loop works with promises here: https://blog.sessionstack.com/how-javascript-works-event-loop-and-the-rise-of-async-programming-5-ways-to-better-coding-with-2f077c4438b5
But at what point of execution does it become asynchronous?
When something happens "out of order".
Is Promise.resolve() asynchronous?
Not usually. But yes, depending on what argument you call it with it sometimes does schedule the assimilation of a thenable for later. In your case, passing an array, no there's nothing asynchronous going on.
In any case, it always immediately returns a promise.
Or is .then asynchronous or even both functions?
Yes. then does always schedule the callback function that you pass in for later.
Of course, it does always immediately return a promise as well.
In general, we can say that observing a promise result is always asynchronous.
When asking whether a function is asynchronous, it's unfortunately ambiguous whether we mean that the function is called asynchronously (later), or causes another function to be called asynchronously (by scheduling an asynchronous event).
I'm reading this article on microtasks and there is the following conclusion:
In summary:
Tasks execute in order, and the browser may render between them
Microtasks execute in order, and are executed:
after every callback,
as long as no other JavaScript is mid-execution at the end of each
task
As I understand each task represents a single VM turn, so as long as no other JavaScript is mid-execution at the end of each task means when call stack is empty. But I don't understand what after every callback mean?
Can anyone please explain and show an example?
Here is the clear example that demonstrates this:
function cb1() {
console.log('cb1');
Promise.resolve('df').then(function promiseMicrotask() {
console.log('promise');
});
}
function cb2() {
console.log('cb2');
}
const element = document.querySelector('div.inner');
element.addEventListener('click', cb1);
element.addEventListener('click', cb2);
In the example above when you click on the div.inner, a browser schedules a task to handle the event and call the callbacks cb1 and cb2. Later it starts executing the task and triggers cb1. Inside cb1 a resolved promise schedules a microtask to run the promiseMicrotask callback. Whenever the current stack that started with cb1 is empty the browser checks the microtaks queue and finds a microtask promiseMicrotask. It triggers it and so it logs promise. Then it proceeds to triggering cb2.
So the microtask promiseMicrotask was processed after the callback cb1 but before cb2 and before the browser finished executing all callbacks in the current task.
What's important here is that all event callbacks are executed in current task.