Blog.


The Thread of Execution in JavaScript

As developers, we must understand how the languages, frameworks, and tools we choose work. This knowledge will not only help us build better software and write cleaner code, it'll help us when our program isn't working as expected or things go awry. In this post, my goal is to summarize the thread of execution in JavaScript or other words, how the browser interprets and processes our JavaScript code.

1. The JavaScript Engine.

Browsers have a built-in JavaScript engine that is responsible for reading and translating JavaScript into machine code. Without this engine, our computers wouldn't understand how to process or execute our JavaScript programs. This is a synchronous process, meaning our code is interpreted one line at a time, from top to bottom.

2. The Global Execution Context.

When the JavaScript Engine is interpreting our code, the first thing it does is create a Global Execution Context. This context wraps all of our JavaScript code and provides a global environment for us to reference/ access any globally scoped variables or functions from the window object.

The Global Execution Context is the first item pushed onto our Call Stack (more on that shortly) and it'll remain there until all of our code has been processed by the JavaScript engine.

3. The Local or Function Execution Context.

When a function is invoked, a Local Execution Context is created. The responsibility of this context is to parse the called function and return the result to our global environment.

The reason functions are processed within a separate context is because they have their own scope. Depending on where the function is called and what is passed to it, some data/ values within the function may not be accessible globally. The separate context helps the JavaScript engine translate our code and reduces parsing errors.

4. The Call Stack.

The Call Stack in JavaScript is a data structure that records and keeps track of where we are in the program. It follows the LIFO principle, last-in, first-out. Meaning our code is executed line by line, from top to bottom (sound familiar?). This is because JavaScript is single-threaded, meaning it cannot multitask.

For example, when a function is invoked, it is pushed to the top of the stack, processed, and then removed or popped from the stack when complete. If the function contains and invokes additional functions, these will be added to the top of the stack and processed before returning to the previous function. Once the function(s) are finished, the JavaScript engine will resume parsing the Global Execution Context (if applicable), check the Queue, and wait for future invocations/ events.

It's okay if this doesn't make sense yet, there is an example of below that'll clear things up.

5. The Queue.

Even though JavaScript is a synchronous language and isn't capable of multitasking, we have Web APIs (DOM, AJAX, and Timers) and some JavaScript features (Callbacks, Promises, Async/ Await) that give us the ability to manipulate and write JavaScript code that behaves asynchronously.

When we utilize these APIs, features, or process a user-related event (such as a click or key press), they are added to the Queue. However, the Call Stack will not accept or process any events from the Queue until it is empty. Meaning the JavaScript engine has to parse our Global Execution Context and any invoked functions before checking for/ processing items in the Queue.

Putting It All Together.

This is a lot of information to take in. Below is an example of a JavaScript program, with lots of logs to the console. Within each console log statement, I've briefly summarized the action that is happening and have prefaced it with a number. The number represents the order it'll print to the console when our program runs.

To demonstrate the Queue, I've used setTimeout() from our browsers Web API. I've set the timeout to zero on both instances, to show that it won't impact the order of our logs even though there isn't an actual delay.

console.log("1. JavaScript Engine is Processing Program.")
console.log("2. Global Execution Context Created. Pushed to Callstack.")

setTimeout(
  () =>
    console.log(
      "9. Added to Queue. Will be processed after Global Execution Context."
    ),
  0
)

function foo() {
  setTimeout(
    () =>
      console.log(
        "10. Added to the Queue, even though I'm within an invoked function."
      ),
    0
  )
}

function bar() {
  console.log("4. Bar Local Execution Context Created. Pushed to Callstack.")
  console.log("5. Bar LEC Finished. Popped from Callstack.")
}

function baz() {
  foo()
  console.log("3. Baz Local Execution Context Created. Pushed to Callstack.")
  bar()
  console.log("6. Baz LEC Finished. Popped from Callstack.")
}

baz()

console.log("7. GEC Finished. Removed From Callstack.")
console.log("8. Callstack is empty and will process events from the Queue.")

Wrapping Up.

I hope this post helped you get a better understanding of the Thread of Execution in JavaScript. If you'd like to dive deeper in to the subject and view more examples, I'd highly recommend the following:

Resources:

Did you find this post helpful?

  • Let's Connect:
All Rights Reserved © 2020