Code:
function foo() {
var a = 10;
setTimeout(() => {
console.log("1:", a);
}, 0);
(function immediate(b) {
console.log("2:", b);
a = 20;
b = 30;
})(a);
new Promise((resolve, reject) => {
console.log("3:", a);
a = 40;
resolve();
}).then(() => {
console.log("4:", a);
});
console.log("5:", a);
}
foo();
Output:
Try programiz.pro
2: 10
3: 20
5: 40
4: 40
1: 40
Explanation:
function foo() {
var a = 10; // (1)
setTimeout(() => { // (2)
console.log("1:", a);
}, 0);
(function immediate(b) { // (3)
console.log("2:", b);
a = 20; // (4)
b = 30; // (5)
})(a);
new Promise((resolve, reject) => { // (6)
console.log("3:", a);
a = 40; // (7)
resolve(); // (8)
}).then(() => { // (9)
console.log("4:", a);
});
console.log("5:", a); // (10)
}
foo();
(1) var a = 10;
- Declares
ainfoo’s scope with value10.
(2) setTimeout(..., 0)
- Schedules a macrotask to run later.
- The arrow function closes over the variable
a(not a copy), so it will print whateverais at the time it runs.
(3) IIFE call immediate(a)
- Calls immediately with
b = 10(the current value ofais passed by value because it’s a primitive).
Inside the IIFE:
console.log("2:", b);→ prints2: 10.a = 20;→ updates the outerato20(novar/let/consthere, so it resolves to theainfoo).b = 30;→ only changes the local parameterb. It does not affecta.
So after the IIFE:
a === 20.
(6) new Promise(executor)
- Creating a Promise runs its executor synchronously.
Inside the executor:
console.log("3:", a);→ prints3: 20(currenta).a = 40;→ updatesato40.resolve();→ queues the.thenhandler as a microtask (to run after the current call stack, but before macrotasks likesetTimeout).
(10) console.log("5:", a);
- Still in the same synchronous turn;
ais now40. - Prints
5: 40.
Microtasks run now
.then(() => console.log("4:", a))executes before anysetTimeout.ais40→ prints4: 40.
Macrotasks run after microtasks
- The
setTimeout(..., 0)callback runs now. ais still40→ prints1: 40.
Key concepts highlighted
- Promise executor runs immediately, but
.thencallbacks are microtasks: they run after the current synchronous code finishes, before timers. setTimeout(..., 0)is a macrotask: it runs after microtasks.- Closures capture variables, not values: the timeout prints the latest
a. - Function parameters are independent variables: reassigning
bdoesn’t changea(primitives are passed by value). - Assigning
a = 20inside the IIFE updates theainfoo’s scope because there’s no localadeclared there.
That’s why the order is:
- IIFE log with original
b→2: 10 - Promise executor log with
a = 20→3: 20 - Sync log after setting
a = 40→5: 40 - Microtask (
then) →4: 40 - Macrotask (
setTimeout) →1: 40