I am trying to make sense of the execution flow of fetch() but it's not consistent with my experimentation.
I found this answer https://stackoverflow.com/a/62566665/15933760
The fetch method will indeed start a network request immediately, which will get processed "in parallel", i.e outside of the event loop. When the the request response will be received (not its body, typically the headers), the browser will queue a new task which will only be responsible of resolving that Promise, and thus of queuing its microtask.
Experimentation
I understand it except for the task/microtask queuing bit. This is what I tried to experiment:
function delay() {
i=0;
while(i<1000000000) i++;
}
console.log('start');
setTimeout(()=> {
console.log('start macrotask');
delay();
console.log('end macrotask');
}, 0);
Promise.resolve().then(() => {
console.log('start promise handler');
delay();
console.log('end promise handler');
});
console.log('calling fetch');
fetch('http://127.0.0.1:9999/').then(function(result) {
console.log('success start fetch handler');
delay();
console.log('success end fetch handler');
},
function(error) {
console.log('error start fetch handler');
delay();
console.log('error end fetch handler');
});
console.log('done calling fetch');
delay();
delay();
delay();
delay();
delay();
delay();
delay();
delay();
delay();
delay();
delay();
delay();
delay();
Promise.resolve().then(() => {
console.log('start 2nd promise handler');
delay();
console.log('end 2nd promise handler');
});
setTimeout(()=> {
console.log('start 2nd macrotask');
delay();
console.log('end 2nd macrotask');
}, 0);
delay();
console.log('end');
In my Chrome console this outputs:
start
calling fetch
done calling fetch
end
start promise handler
end promise handler
start 2nd promise handler
end 2nd promise handler
undefined
success start fetch handler
success end fetch handler
start macrotask
end macrotask
start 2nd macrotask
end 2nd macrotask
Looking at my local traffic, a connection and request are made to 127.0.0.1:9999 right when it outputs calling fetch
. Furthermore, if I program the socket to not respond immediately, the execution continues and I see done calling fetch
right away.
So far this is all consistent and shows that fetch executes in parallel with the main thread.
The Problem
- The first main thread is executing so we see
start
- setTimeout callback gets queued as a macrotask
- The first promise's handler gets queued as a microtask
- we see
calling fetch
done calling fetch
as fetch() continues to execute in parallel even after that - The main thread waits plenty of time with
delay()
calls in addition to me looking at traffic to witness the connection forming, request and reply getting sent, and the socket connection closing up cleanly - fetch() completing its job in parallel, I expect it to either
- Option 1 fulfill the promise and therefore queue its handler as a microtask, or
- Option 2 queue a task (macrotask) which would then fulfill the promise.
Option 1
- The promise handler gets queued as a microtask
- Following the rest of the code, it queries another microtask (that last promise)
- Followed by queuing a macrotask (last setTimeout call)
This leaves us with queues looking like this:
microtask queue: first promise handler
fetch handler
last promise handler
macrotask queue: first timeout callback
last timeout callback
This isn't consistent because the order we see from the output is:
first promise handler
last promise handler
fetch handler
first timeout callback
last timeout callback
Option 2
- A macrotask that fulfills the fetch promise gets queued
- Following the rest of the code, it queries a microtask (that last promise)
- Followed by queuing a macrotask (last setTimeout call)
This leaves us with queues looking like this:
microtask queue: first promise handler
last promise handler
macrotask queue: first timeout callback
fetch task
last timeout callback
This again isn't consistent because the order we see from the output is:
first promise handler
last promise handler
fetch handler
first timeout callback
last timeout callback
To be consistent it should execute the promise-resolving task after the first timeout callback task.