Asynchronous JS stuff
Published: 2024-01-07 21:05:54

- 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](https://frontendmasters.com/courses/javascript-new-hard-parts/) 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`. The `global 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](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Introduction#apis_in_client-side_javascript)), 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 (the `call stack` is empty) and 2) the `call stack` is empty. The stuff "outside" (the browser feature) gets added the the `callback queue` (called `task queue` in the specs) before it's allowed back in. Only when 1) and 2) are done, JS looks inside the `callback queue` and adds it in the `call stack`. That's called the `event loop`. And only when the function `printHello` 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 (a `Promise` object), which acts as a placeholder of the data that will come later. The data will eventually be in the `value` property. Promises have an `onFulfilled` hidden property that runs when the data is available (array of functions that run when `value` gets updated). "Two pronged" solution: JS 1) returns the `Promise` with a placeholder and 2) initiates the browser work (xhr request). We don't know when `value` will be filled with data, so we add functions inside the `onFulfilled` that will run once we have the data, with `value` as input to these functions. `.then` adds a function to the `onFulfilled` array. When these functions run, they are added to the `microtask queue` (`job queue` in the specs). The event loop prioritises tasks in the `microtask queue` over the `callback queue` (the `microtask queue` should be empty before the `callback queue` is allowed in the `call stack`. The promise has another properties, `status` with 3 possible values: `pending`, `fulfilled` and `rejected`. The `rejected` status will trigger a similar array to `onFulfilled`, but called `onRejected`. We add function there with the `.catch` (or the 2nd argument to `.then` (see mdn [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch)). 
- `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 call `createFlow` (`createFlow()`), it returns a `Generator object` (mdn docs [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator)) with a `next` method ([here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator#instance_methods)), 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 an `execution context`, with a local memory. When `yield` is reached, the execution of this context is "paused". The returned value of `next` is:

```
{
    value: 4,
    done: false, // or true if we are yielding the last value
}
```
- When we pass values to `next` the first `yield` will not return any value, because of the power of the `yield` keyword which, like `return` kicks us out of the execution context. Next time we call `next` with a value, the previous `yield` will evaluate to that value, and the execution resumes. See example in the course (~17.25 in `Generator Functions with Dynamic Data` lesson) and in [mdn](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/next#sending_values_to_the_generator).
- The "backpack" (`closure`) of the `generator` has not also the line where execution "stopped", which allows us to continue execution when `next` 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 of `createFlow` gets assigned (``generator` object); 2) create const `futureData` 3) we open `createFlow` execution context and we reach the `yield` keywords, which "pauses" the execution of the function and allows to assign to `futureData` the `promise` object created by calling the `fetch` API; 3) `.then` adds that function to the `onFullfilled` array in the `promise` object (it will get triggered when the value of the promise is updated); 4) the `fetch api` returns the data, and the function inside `onFullfulled` gets added into the `microtask` queue (`job queue` officially) and, since the call stack is empty + no more code to run, it gets added into the `call stack`; 5) `doWhenDataReceived` execution context starts,  it calls the `.next` from the `generator` object, which brings us back to `createFlow` where the execution was paused; 6) `data` gets assigned the value of the `promise`, and then we resume execution of the rest of the code inside that execution context.
- `async` / `await` simplifies all this. The resumption of `createFlow` 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 to `yield`, it throws you out of the execution context. 1) define `createFlow`; 2) call `createFlow` and open an execution context; 3) console log "me first"; 4) define `data` variable - we don't know what it will evaluate to but the fetch creates a `promise` object, which triggers a browser xhr request - the `promise object` has the `value` property and the `onFullfilled`; 5) `await`, like `yield`, kicks us out of the execution context and pauses it; 6) console log "me second" runs; 7) when the data is back, the execution of `createFlow` resumes by assigning the variable `data` to the value of the `promise`; 8) console log `data`.