Linked Promise.then
calls are nested, not sequential
According to MDN's Microtask guide,
if a microtask adds more microtasks to the queue by calling queueMicrotask(), those newly-added microtasks execute before the next task is run.
However, this does not apply to linked .then
calls. If it did, you would have to operate under the assumption that linked .then
calls are all linked to the original Promise and not each other. You can actually see that each linked .then
call is linked to the result of the previous .then
:
Promise.resolve(3)
.then(x => { // Receives 3 from original Promise
console.log(x)
x = x ** 3
return x
})
.then(x => { // Receives 27 from previous .then
console.log(x)
x = x ** 3
return x
})
.then(x => { // Receives 19683 from previous .then
console.log(x)
return x
})
In this example, you can see that in order for the next .then
to be added to the queue, it needs the result of the previous .then
call.
To further illustrate that linked Promise.then
calls are nested and not sequential, you can create both nested and sequential microtasks to see what happens.
Microtasks
Nested
When this example is run, it clearly illustrates the same results you received from linking .then
calls.
queueMicrotask(() => {
console.log('A1')
queueMicrotask(() => {
console.log('A2')
queueMicrotask(() => {
console.log('A3')
})
})
})
queueMicrotask(() => {
console.log('B1')
queueMicrotask(() => {
console.log('B2')
queueMicrotask(() => {
console.log('B3')
})
})
})
Sequential
When you run this example, it demonstrates the aforementioned quote from MDN. It functions similarly to how you thought linking .then
calls would, but this is not how Promises work.
queueMicrotask(() => {
console.log('A1')
queueMicrotask(() => console.log('A2'))
queueMicrotask(() => console.log('A3'))
queueMicrotask(() => console.log('A4'))
})
queueMicrotask(() => {
console.log('B1')
queueMicrotask(() => console.log('B2'))
queueMicrotask(() => console.log('B3'))
queueMicrotask(() => console.log('B4'))
})
Promise conceptualization
If you don't care about this part, you can skip it. This is simply a quick implementation of a Promise
using queueMicrotask
. Hopefully it can help you see the nesting of linked .then
calls and their associated microtasks.
class MicroPromise {
#result = undefined
#thens = []
constructor(callback) {
queueMicrotask(() => {
callback(result => {
this.#result = result
this.#callThens()
})
})
}
then(callback) {
return new MicroPromise(resolve => {
this.#thens.push({resolve, callback})
})
}
#callThens() {
queueMicrotask(() => { // Allows us to wait for `.then` MicroPromise constructor microtasks to execute
for (const then of this.#thens) {
queueMicrotask(() => then.resolve(then.callback(this.#result)))
}
})
}
static resolve(result) {
return new MicroPromise(resolve => resolve(result))
}
}
MicroPromise.resolve()
.then(() => console.log('A1'))
.then(() => console.log('A2'))
.then(() => console.log('A3'))
MicroPromise.resolve()
.then(() => console.log('B1'))
.then(() => console.log('B2'))
.then(() => console.log('B3'))