- https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
- https://www.youtube.com/watch?v=8aGhZQkoFbQ
- https://www.youtube.com/watch?v=cCOL7MC4Pl0
- https://frontendmasters.com/courses/javascript-hard-parts-v2/
Notes from The hard parts of asynchronous Javascript
course in Frontendmasters, by Will Sentance. The links above are related to the topic.
- When the code runs:
global execution context
. 1)Thread of execution
(parsing and execution line by line) and 2)Global Variable Environment
(memory of variables with data). - When a function gets executed, we create a new
execution context
. 1)thread of execution
(go through the code in the function line by line) 2)local memory
(variable environment
) where everything in the function is stored (garbage-collected when the execution context is over). - Keeping track of the execution context needs the
call stack
. Theglobal execution context
will always be at the bottom when we are running the code. - Synchronicity and single threaded are two pillars of JS. How to make it asynchronous not to block the single thread?
- Web APIs are not part of JS (see here), but allow JS to do more stuff. Web APIs are in the
web browser
. - Stuff outside JS is allowed back in (i.e.
setTimout(printHello, 1000)
) when 1) I have finished running all my code (thecall stack
is empty) and 2) thecall stack
is empty. The stuff "outside" (the browser feature) gets added the thecallback queue
(calledtask queue
in the specs) before it's allowed back in. Only when 1) and 2) are done, JS looks inside thecallback queue
and adds it in thecall stack
. That's called theevent loop
. And only when the functionprintHello
is added to the call stack the code inside will run (printHello
doesn't run "inside"setTimeout
). -
Promises
interacts with this world outside JS but it immediately returns an object (aPromise
object), which acts as a placeholder of the data that will come later. The data will eventually be in thevalue
property. Promises have anonFulfilled
hidden property that runs when the data is available (array of functions that run whenvalue
gets updated). "Two pronged" solution: JS 1) returns thePromise
with a placeholder and 2) initiates the browser work (xhr request). We don't know whenvalue
will be filled with data, so we add functions inside theonFulfilled
that will run once we have the data, withvalue
as input to these functions..then
adds a function to theonFulfilled
array. When these functions run, they are added to themicrotask queue
(job queue
in the specs). The event loop prioritises tasks in themicrotask queue
over thecallback queue
(themicrotask queue
should be empty before thecallback queue
is allowed in thecall stack
. The promise has another properties,status
with 3 possible values:pending
,fulfilled
andrejected
. Therejected
status will trigger a similar array toonFulfilled
, but calledonRejected
. We add function there with the.catch
(or the 2nd argument to.then
(see mdn here). -
Closure
===lexical scope reference
.Iterator
is a function that when called and executes something in an element it gives me the next element in the data. Iterators turn our data into "streams" of data, actual values that we can access one after another. The "closed over" data is stored in a hidden property called[[scope]]
. -
Generators
. Defined with a*
in the function declaration (i.e.function* createFlow() {}
). When we callcreateFlow
(createFlow()
), it returns aGenerator object
(mdn docs here) with anext
method (here), that can be called afterwards. Like the code below. Generator functions do not have arrow function counterparts.
function *createFlow() {
yield 4;
yield 5;
}
const returnNextElement = createFlow()
const elementOne = createFlow.next()
const elementTwo = createFlow.next()
- Calling the
.next
of a generator object creates anexecution context
, with a local memory. Whenyield
is reached, the execution of this context is "paused". The returned value ofnext
is:
{
value: 4,
done: false, // or true if we are yielding the last value
}
- When we pass values to
next
the firstyield
will not return any value, because of the power of theyield
keyword which, likereturn
kicks us out of the execution context. Next time we callnext
with a value, the previousyield
will evaluate to that value, and the execution resumes. See example in the course (~17.25 inGenerator Functions with Dynamic Data
lesson) and in mdn. - The "backpack" (
closure
) of thegenerator
has not also the line where execution "stopped", which allows us to continue execution whennext
is called. - In asynchronous JS (i.e.
fetch
):
function doWhenDataReceived(value) {
returnNextElement.next(value);
}
function* createFlow() {
const data = yield fetch(url);
console.log(data);
}
const returnNextElement = createFlow();
const futureData = returnNextElement.next();
futureData.then(doWhenDataReceived)
- ^ 1) Create const
returnNextElement
to which the return value ofcreateFlow
gets assigned (`generator
object); 2) create constfutureData
3) we opencreateFlow
execution context and we reach theyield
keywords, which "pauses" the execution of the function and allows to assign tofutureData
thepromise
object created by calling thefetch
API; 3).then
adds that function to theonFullfilled
array in thepromise
object (it will get triggered when the value of the promise is updated); 4) thefetch api
returns the data, and the function insideonFullfulled
gets added into themicrotask
queue (job queue
officially) and, since the call stack is empty + no more code to run, it gets added into thecall stack
; 5)doWhenDataReceived
execution context starts, it calls the.next
from thegenerator
object, which brings us back tocreateFlow
where the execution was paused; 6)data
gets assigned the value of thepromise
, and then we resume execution of the rest of the code inside that execution context. -
async
/await
simplifies all this. The resumption ofcreateFlow
is done automatically. But this still gets added to the microtask queue:
`
async function createFlow() {
console.log("Me first");
const data = await fetch(url);
console.log(data);
}
createFlow();
console.log("Me second");
`
-
await
is similar toyield
, it throws you out of the execution context. 1) definecreateFlow
; 2) callcreateFlow
and open an execution context; 3) console log "me first"; 4) definedata
variable - we don't know what it will evaluate to but the fetch creates apromise
object, which triggers a browser xhr request - thepromise object
has thevalue
property and theonFullfilled
; 5)await
, likeyield
, kicks us out of the execution context and pauses it; 6) console log "me second" runs; 7) when the data is back, the execution ofcreateFlow
resumes by assigning the variabledata
to the value of thepromise
; 8) console logdata
.