In the previous chapters you learned how to automate your TypeScript development environment. We also learned how to put into practice type annotations, function, classes and other object oriented programming principles and elements. Modules were deliberately omitted because they deserve their own chapter.

  • Introduction
  • The event loop
  • AJAX
  • Callbacks
  • Arrow functions
  • Promises
  • Promise chains
  • Parallel and sequential execution of promises
  • Generators
  • Using generators to run asynchronous code in a way that looks synchronous
  • Async and await
  • Web workers

5.1 Introduction

In this chapter we are going to try and master the usage of callbacks, promises, generators, the async and await keywords, web workers and service workers. All these APIs allow us to execute a task asynchronously but, before we jump straight into the action, it is important to learn some basic concepts about the JavaScript runtime. The JavaScript runtime is extremely important when working with TypeScript because the TypeScript code that we write at design-time is just JavaScript at runtime.

The first thing that we need to know is that JavaScript runtime uses one single thread. This means that two tasks cannot be executed simultaneously and, therefore, JavaScript cannot provide us with real concurrency. The JavaScript concurrency model is based on an event loop. The event loop earned its name because its implementation can be showcased using the following code snippet:

while(queue.waitForMessage()){
  queue.processNextMessage();
}

Even though, the preceding code snippet is an extremely simplified version of the real implementation of the real world it should help us to get an idea of how it works. In order to understand how the JavaScript event loop works, we must first learn some core concepts:

  • Messages and queue: The event loop adds new messages to a queue. Each message is associated with a function. When the stack is empty, a new message is taken from the queue and processed.
  • Frames and stack: When a message is processed the function associated with the message is invoked and a new frame is added to the top of the stack. The stack is a last-in-first-out (LIFO) data structure, which contains a list of frames. If the function invokes other functions, they are added to the stack as well. As the functions returns they are removed from the stack.

    A good way to understand the stack and frames is to think about what happens when a function call itself. A frame is added to the stack with each call and after a few iterations, a stack overflow exception is thrown because the stack contains too many frames.

  • Heap: Variables and objects are allocated in the heap, which is an unstructured region of memory.

The following diagram is an abstract representation of the heap, queue, messages, stack and frames:

The runtime synchronously waits for messages to arrive and process the next message if messages are available in the queue. When an event takes place, if the event has an event handler, a new message is added to the queue.

The event loop follows a rule known as “run-to-completion”, which means that once a message is being processed, no other messages will be processed until the execution of the message being processed has finalized. A downside of this model is that if a message takes too long to complete, the application will not be able to process any other messages and will become unresponsive.

One the best characteristics of the event loop model is it never blocks. JavaScript handles I/O via events and callbacks, so when the application is waiting for an AJAX request to return, it can still process other things like user input.


Shiv Kushwaha

Author/Programmer