Open CheatSheet Back To List Next Tutorial

Event Loop

Intro

  • This tutorial covers the JavaScript Call Stack and Event Loop.

  • It is recommended that you follow along with each section below. Create a folder called event-loop. Paste or type the code into your own JavaScript and HTML files. 

  • The example code for the entire tutorial series is at github.com/LearnByCheating/javascript-tutorial. There is a separate folder called event-loop that holds the files from this tutorial. The Readme file has instructions on how to run the code in the Browser environment.

  • We will start with some definitions.

Definitions

  • JavaScript: is a scripting language, which means you don't compile it to machine code ahead of time. Instead the script is interpreted by another program. JavaScript is interpreted the JavaScript engine which is a component of web browsers and Node.js. JavaScript consists of the core JavaScript language plus Web APIs.
  • Runtime: is the stage of the programming lifecycle when the program is running. 
  • Runtime environment (or runtime system): is the environment in which a programming language is run. JavaScript has two principal runtime environments, the browser and Node.js.
  • Core JavaScript: is the JavaScript programming language without the Web APIs. It is officially called ECMAScript, and its standards are set by the Ecma International organization. It includes the language syntax, operators, data types, constructs like if statements and loops, and built-in global objects like String, Number, Boolean, Object, Array, Function, etc.
  • JavaScript Engine: is an interpreter that parses and executes JavaScript code. It is part of the JavaScript runtime environment. The main JavaScript engines are V8 built by Google and used in Chrome, Edge, and Node.js. SpiderMonkey used in Firefox, and JavaScriptCore used in Safari.

Call Stack: overview and definitions

  • The JavaScript engine manages the heap memory and the call stack.
  • Heap Memory: is a Region of computer memory, mostly unstructured, that stores objects, including functions.
  • The Call Stack: keeps track of nested function calls. A stack is a data structure that follows the Last In First Out (or LIFO) principle. Think of a stack of plates where each plate is a function call that is in progress. The first function called goes on the bottom. When it finishes it is taken off the stack. But if it calls another function before it completes then the function it calls gets placed on top of the stack. 
    • The function itself is stored in heap memory. What goes on the stack is called a stack frame. It includes the function name, its arguments, and the line the function call was made, or where the code is currently at.

1. Call Stack: example

  • Let's go through an example of how the call stack works.

  • Below is a transaction to buy a book online.
  • There are three functions: orderBook, checkStock, and chargeCard.

const bookId = 'abc123';
const price = 29.95;
const creditcard = '1111222233334444';
const status = orderBook(bookId, price, creditcard); // Step 1
function orderBook(bookId, price, creditcard) {
  const inStock = checkStock(bookId); // Step 2
  if (!inStock) return 'Out of stock';
  const paid = chargeCard(price, creditcard);// Step 4
  if (!paid) return 'Credit Card denied';
  return 'Order complete'; // Step 6
}
function checkStock(bookId) { // Step 3
  console.log(`Checking database for bookId: ${bookId}`);
  return true; // true if in stock, false if not
}
function chargeCard(price, creditcard) { // Step 5
  console.log(`Charging card $${price}`);
  return true; // true if successfully charged, false if not
} console.log(status); // logs "Order is complete"

  • Starting point: Before the orderBook function is called, there are no functions in the call stack.

  • Step 1: The orderBook function is called passing in the needed information in the arguments: bookId, price, creditcard.
orderBook is now on the call stack.

  • Step 2: The checkStock function is called.
checkStock is added to the top of the call stack.
orderBook is on the bottom.

  • Step 3: The stock is checked. The bookId is in stock so checkStock returns true and is removed from the top of the call stack.
orderBook is still on the call stack.

  • Step 4: The orderBook function calls the chargeCard function passing in the price and creditcard variables.
chargeCard is added to the top of the call stack.
orderBook is on the bottom.

  • Step 5: The card is charged successfully so the chargeCard returns true and is removed from the top of the call stack. 
orderBook is still on the call stack.

  • Step 6: The orderBook function is complete and returns the string "Order is complete". The call stack is now empty. 


Web APIs and the Event Loop: overview and definitions

  • Before moving on we'll give quick definitions of synchronous and asynchronous.
    • Code executed synchronously means the program waits until each line of code is complete before moving on to the next line. Core JavaScript executes synchronously.
    • Code executed asynchronously means when an asynchronous function is called, the code immediately moves on to the next line without waiting for the asynchronous function to do its task.

  • Below is a diagram of the JavaScript Browser Runtime Environment. On the left side is the JavaScript engine which executes core JavaScript code, and manages the Call Stack and Heap memory. When we covered the call stack in the last section we did not use any Web APIs. All the code was executed synchronously within the JavaScript engine. 
  • Now let's look at Web APIs. Some execute synchronously, while others execute asynchronously and involve the Callback Queue and the Event Loop. 

  • API: stands for Application Programming Interface. It is an interface for computer programs to communicate with each other. 
  • Web APIs: are APIs in the Web browser. There are multiple interfaces attached to the window object that you can access in your JavaScript code. The DOM APIs are the most used. They let you modify elements on a web page without having to refresh the page. Other examples of Web APIs include the fetch API which lets you fetch data from the server. And Storage APIs which store and access data in the browser. 
    • Web APIs are called from your script but are executed outside the JavaScript engine by the browser using lower level programming languages like C++.
  • Node I/O APIs: The Node.js Runtime Environment has Input/Output APIs to access core Node.js modules and third party npm packages. Examples include the fs module which allows interacting with the computer's file system. The http module which creates a web server, receives HTTP requests and sends responses. Similar to the browser, Node has a console module and a timer module with setTimeout and setInterval. There are third party npm packages to handle specialized tasks like interacting with a database, creating a web framework, and much more.

  • Events: In JavaScript, events are fired to notify your code of changes that may affect code execution. 
    • On a web page, events are actions or occurrences that happen to HTML elements. They can be user actions on the DOM such as clicking a mouse button, or state changes like a web page load completing. 
    • Web and Node.js APIs emit events when an asynchronous task is complete. 

  • Single Treaded: JavaScript is a single threaded programming language. That means it has one call stack and one memory heap, So it can only execute one sequence of code at a time. Multi-threaded programming languages can split code sequences into separate threads and process them concurrently, but at the cost of managing the multiple threads.
  • Synchronous (sync): Because JavaScript is single threaded, core JavaScript code is executed synchronously. That means the code is executed in order, one line at a time. It is blocking, so the next statement can't begin until the current statement is complete. Some Web APIs execute synchronously including console.logs, Web storage, and DOM APIs without event listeners.
  • Asynchronous (async): Web APIs and Node.js modules are executed outside of the JavaScript engine. Some calls to Web APIs and Node Modules execute asynchronously. That means:
    • They are non-blocking. JavaScript does not wait for the result before moving on to the next line. 
    • They are processed concurrently outside of the JavaScript engine. 
    • When complete, the API generates an event, passing the result to a callback function, which is added to the callback queue, then placed on the call stack and executed by the JavaScript engine. Examples of asynchronous Web APIs include DOM APIs with event listeners attached to them (e.g., waiting for a button to be clicked), timer functions like setTimeout, and the fetch API. Node.js has asynchronous modules like part of the file system module, the http module, and Node's version of setTimeout. And there are asynchronous methods in npm packages such as interacting with a database.

  • The Event Loop: monitors both the Call Stack and the Callback Queue. When the call stack is empty, it checks the queue. It places the first callback in the queue (the oldest one) onto the call stack to be executed. 
    • When the call stack is empty again, the event loop grabs the next callback in the Queue. If there aren't any, the event loop pauses and waits for one to come. 


2. Synchronous Web APIs

  • Synchronous Web APIs behave like core JavaScript code in that they are executed immediately and finish what they are doing before the next line of code is executed.

  • Below are examples of Web APIs that execute synchronously. 

Examples:
const result = document.getElementById('result');

sessionStorage.setItem('name', 'Joey');
let name = sessionStorage.getItem('name'); // gets the name item from browser storage.

result.textContent = name; // displays "Joey" on the web page.
console.log(name); // logs "Joey" sessionStorage.removeItem('name');

  • The first statement uses a DOM Web API to get the element with id="result" from the web page and assigns it to variable result.
  • The next two statements: 
    • Access the Storage Web API sessionStorage to set an item called "name" to the value "Joey". 
    • Then get the stored name and assign it to variable name
  • The next two statements:
    • Use a DOM Web API to access the result element and assign the name value to it. "Joey" gets displayed on the web page.
    • Call the console Web API and log the name value.
  • On the last line the "name" item is removed from session storage.


Asynchronous Web APIs: example

  • To explain how asynchronous Web APIs work we will use a few examples.
  • First let's set up the files.

  • Below is a simple web page that has a button element with id="btn".
  • And an empty div element with id="result".
  • The script tag with the src source attribute inserts the event-loop.js JavaScript file.

index.html
<!DOCTYPE html>
<html lang="en">
<body>
  <button id="btn">Click me</button>
  <div id="result"></div>
<script src="event-loop.js"></script>
</body>
</html>

  • Below is the JavaScript file.
event-loop.js
const btn = document.getElementById('btn');
const result = document.getElementById('result');
btn.addEventListener('click', function eventHandler() {
  result.textContent = 'Button was clicked';
});

  • The script calls several DOM Web APIs including Document, EventTarget, and Node. Document and Node are synchronous while EventTarget is asynchronous. 
  • The first two statements use the Document API to get the elements with id's "btn" and "result" and assign them to variables btn and result.

  • The we add an event listener to the btn element which uses the EventTarget API to listen for the click event. This is asynchronous because it is not executed until the click event occurs on the button.
  • When the click event occurs the callback function uses the Node API to set the textContent of the result element to "Button was clicked".

  • Let's walk through how this works in terms of the DOM Web APIs, the callback queue, and the event loop. 

  • Starting point: The web page and the JavaScript file have been loaded. In our script we attached an event listener to the button element, listening for the click event.  

  • Step 1: The user clicks the button triggering the event listener.
  • Step 2: The EventTarget API places the eventHandler function onto the Callback Queue. There are no other callback functions waiting in the queue so the eventHander function goes to the front.
  • Step 3: If or when there are no other functions on the call stack, the Event Loop places the eventHandler function on the call stack.
  • Step 4: The eventHandler function gets invoked. It sets the result.textContent property to "Button was clicked".  The Node API adds the text to the web page synchronously.

  • Let's look at an asynchronous example with the setTimeout function. 
  • Add the below event listener and callback function.
event-loop.js
btn.addEventListener('dblclick', function eventHandler() {
  console.log(1, 'First statement.');  
  setTimeout(function twoSecondsAsync() {
    console.log(2, 'SetTimeout callback, logs after 2 seconds');
  }, 2000);
  console.log(3, 'Third statement.');

  setTimeout(function noDelayAsync() {
    console.log(4, 'SetTimeout callback, logs after 0 seconds');
  }, 0);

  console.log(5, 'Last statement.');
});

  • The event listener is on the button element listening for the "dblclick" event. On the web page, open the console then double-click the button. That will trigger both the click and dblclick events. 
    • It will immediately log:
1 First statement.
3 Third statement.
5 Last statement.
4 SetTimeout callback, logs after 0 seconds
  • Then after a two second delay it will log:
2 SetTimeout callback, logs after 0 seconds

  • Starting point: The web page and JavaScript file are loaded and we have an evert listener registered on the btn button element listening for the dblclick event. 

  • Dblclick event: When the user double-clicks the button it triggers the event listener: 
    • The eventHandler callback function is placed in the callback queue. 
    • The callback queue is empty so it goes to the front. 
    • Event Loop monitors the call stack. When empty the event loop places the eventHandler function on the call stack.
    • The eventHandler function gets invoked.

  • Statement 1: logs 1 First statement to the console.
  • Statement 2: calls the setTimeout function. The browser starts counting down the 2 second timer.
  • Statement 3: logs 3 Third statement to the console.
  • Statement 4: calls the setTimeout function. The delay is 0 so the noDelayAsync() callback function is immediately put on in the callback queue, but it doesn't get put on the call stack until the rest of the script statements are executed.
  • Statement 5: logs 5 Last statement to the console.
  • The noDelayAsync() callback function gets placed on the call stack and logs: 4 SetTimeout callback, logs after 0 seconds
  • The SetTimeout Web API counts down 2 seconds, then places the twoSecondsAsync() callback function into the callback queue. The Event Loop places it on the call stack. It gets invoked and logs: 2 SetTimeout callback, logs after 2 seconds


Conclusion

That concludes this tutorial on the Call stack and Event Loop. 

The event loop is covered in the JavaScript CheatSheet Promises category.

If you have the CheatSheet desktop app and downloaded the JavaScript CheatSheet, then go through the flashcards for the event-loop (part of the Promises category).
Open CheatSheet Back To List Next Tutorial