Asynchronous JavaScript

Lesson

JavaScript is a single threaded language. This means it has one call stack (where code is executed) and one memory heap (where objects are stored). JavaScript executes code in order and must finish executing a piece of code before moving onto the next. We call this synchronous, or blocking, execution. Other languages such as C++ and Java are multi-threaded and can execute multiple pieces of code at the same time. We refer to this as asynchronous or non-blocking execution.

What is the Call Stack?

The call stack is a data structure in the runtime of javascript that functions to organise the running or 'execution' of your code.

an animation of a stack

Last on first off. Imagine a stick over which you can place hoops. To get to the bottom hoop, all the other hoops have to come off first.

stack of hoops
.

How does JavaScript use the Call Stack?

Lets take the following code example:

function multiply(a, b) {
    return a * b
}
function square(n) {
    return multiply(n, n)
}
function printSquare(n) {
    const result = square(n)
    console.log(result)
}

printSquare(4)

Read the code above. First of all there are 3 function definitions, then one of those functions is called. When printSquare is called it is put onto the stack. printSquare is evaluated and calls square which is added to the stack, square calls multiply which is added to the stack. multiply does not call any other function so it returns the value 16. the return keyword means that function pops off the stack, now inside square that function is evaluated to 16, and returns so square is then popped off the stack. Now back in printSquare the called to square is evaluated and assigned in memory to the variable result. Next line console.log is called with 16 and the function implicitly returns (without a value) as there is nothing more to execute. See below:

call stack

Loupe is a little visualisation to help you understand how JavaScript's call stack/event loop/callback queue interact with each other) is a tool which helps you visualise how JavaScripts Call Stack, Event Loop and Callback Queue interact with each other.

Open up this link illustrating how synchronous code is executed

Can you see how each line is executed one at a time? Experiment by adding in the functions code above (make sure you add a line of code which calls the printSquare function to kick it off!).

What is a stack trace?

The stack is very help to know about. When your code errors, you often get a 'stack trace' as part of the error message. Being able to read the 'stack trace' can help us follow the executing code, and that can lead us to our piece of code that is causing the error.

Try running the code below in your browser.

function multiply(a, b) {
    throw new Error(`can't multiply ${a} and ${b}`)
}
function square(n) {
    return multiply(n, n)
}
function printSquare(n) {
    const result = square(n)
    console.log(result)
}

printSquare(4)

This is the error. Read the stack trace from the bottom up.

stack trace

What do you think the numbers like (<anonymous:5:12>) refer to?

Stack overflow

function hello() {
    hello()
}
hello()

This will break.

stack overflow error message

Can you explain what is going on here? What other code might cause a max call stack size exceeded (stack overflow)?

Providing asynchronous behaviour

Now you are familiar with the Call Stack, imagine what the impact will be if a function takes a long time to execute or has to wait on something, this function will stop all the other code executing. So how can we avoid this?

In the Browser (for front-end JavaScript) and Node (for back-end JavaScript), JavaScript run inside a runtime 'container'. The runtime includes additional components which are not part of JavaScript. These include:

The Event Loop is what allows asynchronous (non-blocking) operations to occur — despite the fact that JavaScript is single-threaded.

Asynchronous (async) functions such as setting times, reading files etc. are recognised by Node.js and are executed in a separate area from the Call Stack. Node polls (regularly checks) the computer for the completion of the async operation and, once the operation is complete, the callback is placed into the Callback Queue. The Event Loop waits for the Call Stack to be empty and then moves the pending callback onto the Call Stack. It wait as otherwise it would randomly interrupt the execution of whatever sequence of function calls were queued up on the stack.

Below is an example.

async call stack

Timers

JavaScript does not have a built in timer feature. It uses the Timer API provided by the Node.js runtime to perform time-related operations. For this reason, timer operations are asynchronous.

setTimeout(callback, millis) can be used to schedule code execution after a designated amount of milliseconds. It accepts a callback function as its first argument and the millisecond delay as the second argument.

When setTimeout is called, the timer is started in Web APIs part of the Node/Browser runtime. This frees the Call Stack up to continue executing code and only when the timer is complete and the Call Stack empty, does the callback get pushed to the Call Stack for execution.

Click on this link which uses Loupe to illustrates async timers.

Does the timer callback get executed at exactly 5 second after the timer is started? If not, why not?

Assignment

Make an animation or slide show that illustrates the event loop for the following piece of code. app.post is a route handler from an 'express' server it takes a string that is a path, and a route handler function. The route handler function is called when the server receives a POST request to the /users route. Start your stack with a call to the createUser function.

The route handlers in express are all on a timer, so if you don't call response.render or response.send within a time limit express will return a timeout error. Don't worry about this for now. Note that functions without a name are referred to as 'anonymous' functions.

Be ready to present your slides or animation back to the group.

app.post('/users', function createUser(request, response) {
    User.findOrCreate({ where: request.body })
        .then(function (user) {
            user.getContacts()
                .then(contacts => {
                    request.session.userId = user.id
                    response.render('profile', {user, contacts})
                })
        })
    logging(`/users route called with ${request.body}`)
})

Assignment extension tasks

TODO

Additional resources