JavaScript runtimes give each window
access to one "main" CPU thread. An iframe creates a new main window
and therefore gets its own thread. When the main thread is busy running code it can't run other code, so it queues it to be run when the current code has completed.
This is a synchronous stack of procedures filled with the current running procedure and the ancestry of procedures that directly spawned it.
By "procedure" I mean things like: calling functions, assigning values, evaluating conditions, etc…
- The top of the stack is the current procedure.
- The base of the stack is the procedure that started the current run.
- If a procedure calls another procedure (like a function calling another function) then the new function is added to the top of the call stack.
- The procedures inbetween the base and the top are a direct line of procedures that link the base to the top.
- When a procedure completes it is removed from the top of the stack until the stack is empty.
function doOtherStuff (time) {
console.log(time);
}
function doStuff () {
var startTime = Date.now();
doOtherStuff(startTime);
}
doStuff();
With the above code, the call stack would go through the following phases: 1. doStuff
-
Date.now doStuff
-
assign value to startTime doStuff
-
doOtherStuff doStuff
-
console.log doOtherStuff doStuff
-
doOtherStuff doStuff
-
doStuff
-
[ empty ]
While the call stack is populated, no asynchronous code can be run.
So, no code that isn't directly called by an immediately preceding procedure will be run while the callstack is populated. Instead it is queued to be run once the call stack is empty
To manage
When you want to run code, the runtime sends that code to main thread call stack
that thread is busy running code it can't run other code. Any code that is waiting to be run sits in the callback queue. There are a number of ways to add code to this queue.
When the current code has completed running
setTimeout
@chartnett and @andrefuller if you want to use requestAnimationFrame I think you should replace both setTimeout()
s with requestAnimationFrame()
s (or rAF
s for short). But I don't think changing them will change the result because of the effect on the call stack.
All rAF callbacks always run in the same or next frame of work Any rAFs queued in your event handlers will be executed in the same frame. Any rAFs queued in a rAF will be executed in the next frame. All rAFs that are queued will be executed in the intended frame.
https://medium.com/@paul_irish/requestanimationframe-scheduling-for-nerds-9c57f7438ef4#.y772nie5q
setTimeout
will take the function you pass to it and add it to a queue to be run after the current call stack has completely emptied. The time value that you pass to it begins counting down the moment you call setTimeout
but, as you probably know, doesn't mean it will run at that time. So
function doStuff () {
var startTime = Date.now();
setTimeout(function () {
console.log('setTimeout called in ' + (Date.now() - startTime));
}, 0);
doOtherStuff(); // a function that takes about 3000ms to run
}
doStuff();
startTime
would be populatedsetTimeout
will add the function to a queuedoOtherStuff()
runs and takes 3000ms, completing the current call stack- the procedural code completes and the call stack is empty
- the JS engine looks to the queue and runs the function added by
setTimeout
"setTimeout called in 3000ms"
is logged to the console
So knowing that and looking at https://github.com/chartnett/compass/blob/37c2a16ed18b61269445fe5a1fb3a64e70ccf2e7/src/js/modules/creatives/componentBased/views/list.js#L144-L189 we can see on line 149 we add multiple functions to the queue with
setTimeout(function () {
getColumn = componentGrid.getColumnByField(column);
if (getColumn) {
componentGrid.removeColumn(getColumn);
}
}, 0);
Say it creates 4 functions then the queue looks like: [funcA_1, funcA_2, funcA_3, funcA_4]
.
Then the call stack moves on to line 167 and adds another function to the queue.
Now the queue looks like [funcA_1, funcA_2, funcA_3, funcA_4, funcB_1]
The call stack will complete and the JS engine will look at its queue, remove funcA_1
and add it to the call stack, then run it.
As you probably know, when Strand components get their values changed here this triggers the Polymer library to do some magic.
Now (here's the fun part), Polymer code relies on rAF
(@dlasky mailed out some info about it on July 27th, 2016).
So Polymer functions will be added to ANOTHER queue, the rAF
queue.
An "animation frame" (effectively a screen redraw) aims to be done about every 16ms (1000ms/60fps). It only runs when the call stack is emptied. The rAF
queue is a collection of the functions we want to run just before the screen is next redrawn. The functions are run in the order they're added.
Once it has been been 16ms since the last animation frame, the rAF
queue (if it exists) is inserted at the front of the queue.
- call stack is busy
rAF
queue is[funcC_1]
- queue is
[funcA_1, funcA_2, funcA_3, funcA_4, funcB_1]
- call stack is busy and adds to
rAF
queue - rAF queue is now
[funcC_1, funcC_2]
- queue is
[funcA_1, funcA_2, funcA_3, funcA_4, funcB_1]
- call stack is busy and adds to
rAF
queue - rAF queue is now
[funcC_1, funcC_2, funcC_3]
- queue is now
[referenceTo_rAF_queue, funcA_1, funcA_2, funcA_3, funcA_4, funcB_1]
// call stack is freed and runs the first item from queue, the rAF
queue of [funcC_1, funcC_2, funcC_3]
// rAF queue is now []
// queue is now [funcA_1, funcA_2, funcA_3, funcA_4, funcB_1]
If funcA
s take 16+ms to run then
funcA_1() // adds Polymer code to rAF queue
// call stack empties
// >> rAF queued functions are run
funcA_2() // adds Polymer code to rAF queue
// call stack empties
// >> rAF queued functions are run
If funcA
s take 8ms to run then
funcA_1() // adds Polymer code to rAF queue
// call stack empties
funcA_2() // adds Polymer code to rAF queue
// call stack empties
// >> rAF queued functions are run
funcA_3() // adds Polymer code to rAF queue
funcA_4() // adds Polymer code to rAF queue
// call stack empties
// >> rAF queued functions are run