This repo contains small exercises to get more accustom with two ways of dealing with asynchronous code in JavaScript: Promises and the new Async/Await syntax.
This repo contains four big chapters:
Each chapter contains exercises and questions which will walk you threw the journey from callbacks to Promises and Async/Await.
Before everything, we need to talk about JavaScript's asynchronous model, or how does asynchronous programming work in JavaScript.
The most important question we will answer: Is JavaScript an asynchronous programming language?
We all heard about EcmaScript right (or in its other forms: ES5, ES6, ES2015 etc.)? EcmaScript is the standard which describes how the language should look and work. This is a very big document which contains every tiny little detail about the language: how objects intereact, what functions they should contain and how they should work etc.. This official document is written so that future JavaScript engines know how to be built.
We all heard of JavaScript engines like: Chrome's V8, Microsoft's ChakraCore or Mozilla's SpiderMonkey. These engines all follow the EcmaScript standard.
A JavaScript engine has two important internal parts:
- Heap: is the engine's memory where objects, functions etc. get allocated and managed
- Call Stack: because JavaScript is single threaded, it can do only one thing at a time. The call stack manages how functions get executed, in a LIFO kind of fashion.
For funsies, let's open the EcmaScript standard and search for a very common function like: setTimeout
. It should be there, right?
No - we will not find that function in the document. Why you ask? The answer is pretty simple. JavaScript as a language, is a synchronous programming language. However, besides JavaScript engines there is another player we need to talk about, and that player is: JavaScript Runtimes.
A JavaScript runtime is a higher layer over the JavaScript engine which provides extra functionality like libraries, event loops etc. A few examples of runtimes are: Browsers (Chrome, Firefox etc.), NodeJS. A runtime may implement any engine it chooses. This is where the event loop is found, in the JavaScript runtime.
The Event Loop is a runtime level implementation which offers asyncrhonous code running abilities:
Callbacks are the most low levelish way of handling asynchonous code. A callback is a function passed as an argument to another function. The power of this pattern kicks in when combined with an asynchronous function:
function foo(callback) {
setTimeout(() => {
callback()
}, 1000);
}
We can also use callbacks to handle errors:
function hello(success, failure) {
setTimeout(() => {
const chance = Math.ceil(Math.random() * 2);
if (chance == 2) {
success()
} else {
failure();
}
});
}
const succ = () => console.log('yay!');
const failure = () => console.log('I should not play the Loto');
hello(succ, failure);
Solution example 2
function all(callback, arr) {
let counter = 0;
arr.forEach(fn => fn(() => counter++));
const interval = setInterval(() => {
if (counter === arr.length) {
clearInterval(interval);
callback();
}
}, 250);
}
const fn1 = cb => setTimeout(() => {
cb();
}, 1000);
const fn2 = cb => setTimeout(() => {
cb();
}, 1000);
all(() => console.log('done'), [fn1, fn2]);
Chapter 2 - Promises[9]
What will be the output and why?
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
Explanation
With Promises there came a new way of executing async code called: Microtask. This new concept was needed because Promises can guarantee an order of execution even though they are asynchronous. This means that the event loop has another queue for scheduling these kind of microtasks. The main difference between a task and a microtask is that a task different actions can happen between them. After a task is run, the next task can only pushed to the call stack only if nothing else in mid-execution or the call stack is empty and ready to take in another task.
With microtasks however, they have a priority over tasks. If a microtask was queued, it will run before taking in any tasks. Even more, microtasks are processed as long as there are any on the microstack queue. This means that microstack processing can cause a block on the thread as long as you keep schedule them.
This can be better explained through these two examples:
function createTask() {
console.log('new task');
setTimeout(createTask);
}
createTask(0);
function createMicrotask() {
console.log()
Promise.resolve().then(createMicrotask);
}
createMicrotask();
This new syntax was first introduced in ES2017[12]. We now have two main players for writting asyncrhonous functions:
async function() {}
this will create anAsyncFunction
which returns aPromise
.await
theawait
keyword can only be used inside anasync function
(exceptions like Chrome's DevTools).
const fn = async () => Promise.reject('err');
async function foo() {
try {
fn();
} catch(e) {
console.log(e);
}
}
foo();
// err
- Eloquent JavaScript - Chapter 11: Asynchronous Programming
- MDN - Promise
- MDN - async function
- JSConf EU 2014 - Philip Roberts: What the heck is the event loop anyway?
- Message Queue Wiki
- MDN - Concurrency model and Event Loop
- Alexander Zlatkov - How JavaScript works: an overview of the engine, the runtime, and the call stack
- How JavaScript Works: Event Loop and the rist of Async Programming
- Jake Archibald - Tasks, microtasks, queues and schedules
- Jake Archibald: In The Loop - JSConf.Asia 2018
- Async/Await TC39
- ECMAScript® 2017 Language Specification (ECMA-262, 8th edition, June 2017)