- 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 contextwill 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 stackis empty) and 2) thecall stackis empty. The stuff "outside" (the browser feature) gets added the thecallback queue(calledtask queuein the specs) before it's allowed back in. Only when 1) and 2) are done, JS looks inside thecallback queueand adds it in thecall stack. That's called theevent loop. And only when the functionprintHellois added to the call stack the code inside will run (printHellodoesn't run "inside"setTimeout). -
Promisesinteracts with this world outside JS but it immediately returns an object (aPromiseobject), which acts as a placeholder of the data that will come later. The data will eventually be in thevalueproperty. Promises have anonFulfilledhidden property that runs when the data is available (array of functions that run whenvaluegets updated). "Two pronged" solution: JS 1) returns thePromisewith a placeholder and 2) initiates the browser work (xhr request). We don't know whenvaluewill be filled with data, so we add functions inside theonFulfilledthat will run once we have the data, withvalueas input to these functions..thenadds a function to theonFulfilledarray. When these functions run, they are added to themicrotask queue(job queuein the specs). The event loop prioritises tasks in themicrotask queueover thecallback queue(themicrotask queueshould be empty before thecallback queueis allowed in thecall stack. The promise has another properties,statuswith 3 possible values:pending,fulfilledandrejected. Therejectedstatus 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.Iteratoris 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 anextmethod (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
.nextof a generator object creates anexecution context, with a local memory. Whenyieldis reached, the execution of this context is "paused". The returned value ofnextis:
{
value: 4,
done: false, // or true if we are yielding the last value
}
- When we pass values to
nextthe firstyieldwill not return any value, because of the power of theyieldkeyword which, likereturnkicks us out of the execution context. Next time we callnextwith a value, the previousyieldwill evaluate to that value, and the execution resumes. See example in the course (~17.25 inGenerator Functions with Dynamic Datalesson) and in mdn. - The "backpack" (
closure) of thegeneratorhas not also the line where execution "stopped", which allows us to continue execution whennextis 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
returnNextElementto which the return value ofcreateFlowgets assigned (`generatorobject); 2) create constfutureData3) we opencreateFlowexecution context and we reach theyieldkeywords, which "pauses" the execution of the function and allows to assign tofutureDatathepromiseobject created by calling thefetchAPI; 3).thenadds that function to theonFullfilledarray in thepromiseobject (it will get triggered when the value of the promise is updated); 4) thefetch apireturns the data, and the function insideonFullfulledgets added into themicrotaskqueue (job queueofficially) and, since the call stack is empty + no more code to run, it gets added into thecall stack; 5)doWhenDataReceivedexecution context starts, it calls the.nextfrom thegeneratorobject, which brings us back tocreateFlowwhere the execution was paused; 6)datagets assigned the value of thepromise, and then we resume execution of the rest of the code inside that execution context. -
async/awaitsimplifies all this. The resumption ofcreateFlowis 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");
`
-
awaitis similar toyield, it throws you out of the execution context. 1) definecreateFlow; 2) callcreateFlowand open an execution context; 3) console log "me first"; 4) definedatavariable - we don't know what it will evaluate to but the fetch creates apromiseobject, which triggers a browser xhr request - thepromise objecthas thevalueproperty 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 ofcreateFlowresumes by assigning the variabledatato the value of thepromise; 8) console logdata.
Top comments (0)