Expand your code review knowledge: free course access for the first 50 participants

4 min read

JavaScript asynchronous patterns and how to implement them

This article provides a short overview of the concept of asynchronous programming and explores JavaScript asynchronous patterns — generators and iterators — with examples of how to implement them.

The author of this article is EPAM Senior Software Engineer Anjani Tharayil.

Introduction

Are you among those who have felt confused when pondering whether JavaScript is asynchronous or synchronous? The line between these concepts can be unclear even for seasoned developers.

In this article, I explain the principles underlying JavaScript execution, followed by an exploration of synchronous and asynchronous JavaScript. Then, I take a closer look at different asynchronous patterns within JS.

JavaScript execution

JavaScript uses the Just-In-Time (JIT) compilation method. In the first run, it allocates memory for variables and functions. In subsequent runs, it processes the code sequentially. This makes JavaScript a blend of interpretation and compilation, not purely one or the other.

As JavaScript code runs, the global execution context is formed, consisting of two stages: creation and execution.

  • In the creation phase, memory is allocated by scanning through code for variables and functions.

  • The execution phase involves actual code execution.

Commands execute one by one; the next line follows only after the current one completes.

  • Code runs sequentially, with a sole main thread.

  • Long function calls (such as network requests) can block the main thread, causing unresponsiveness.

How to understand asynchronous programming

Asynchronous programming is crucial for modern web development. It is facilitated by the JavaScript event loop — a core concept enabling non-blocking behavior.

The event loop

Imagine JavaScript as a chef managing orders in a busy kitchen. Rather than cooking one dish at a time, the chef switches between tasks, serving everyone faster. Similarly, JavaScript's event loop manages multiple tasks, avoiding blockages. 

The event loop allows JavaScript to perform tasks concurrently without blocking the main thread. This is essential for time-consuming operations like network requests, timers, and interactions, and it keeps the application responsive. 

Here's a high-level overview of how the event loop operates: 

  • Execution stack: Functions execute one at a time, forming a stack. 

  • Message queue: Asynchronous tasks (events, web APIs) await execution.

  • Event loop: Monitors stack, fetches tasks from queue one by one at a time when stack is empty.

  • Task execution: Tasks run in order, each completing before the next task begins.

  • Asynchronous: Non-blocking operations (callbacks, promises) allow scheduling

  • Completion: After async tasks finish, their results join the queue.

  • Event loop continues: Empties queue into stack, maintaining flow.

  • Responsive apps: Enables multitasking, responsiveness in applications.

    How to understand asyncronous programming

    Effectively managing asynchronous behavior is crucial to avoid memory overhead from multiple concurrent operations.

    Control techniques for asynchronous operations:

    • Callbacks: Foundational for async handling, beware of callback hell.

    • Promises: Structured approach, chain operations, use .then() and .catch().

    • Async/Await: ES2017's concise syntax, async functions, await for Promises.

    • Generators and iterators: Pausable functions, async iteration via generators and iterators.

What are generators and iterators?

Generators and iterators are powerful asynchronous patterns in JavaScript that help manage asynchronous control flow and deal with asynchronous operations in a more readable and manageable way. Let's consider each concept separately.

Generators

Generators represent a class of functions that can be halted and then restarted. They are declared using the `function*` syntax. Within a generator function, you can use the `yield` keyword to pause the function's execution and produce a value to the caller. The function can later be resumed from where it was paused.

Example of a generator function:

JavaScript generator function

Generators are particularly useful for asynchronous operations because they allow you to pause execution while waiting for asynchronous tasks to complete and then resume execution when the tasks are done.

Iterators

An iterator is an object that defines a sequence and a way to access that sequence. In JavaScript, iterators are created using the Symbol.iterator method. An iterator must implement a `next()` method that returns an object with two properties: `value` — the current value in the sequence, and `done` — indicating whether the sequence is complete.

Here's a basic example of implementing an iterator:

JavaScript iterator function

Generators can be used to simplify the creation of iterators, especially when dealing with asynchronous operations. By using the `yield` keyword within a generator, you can easily produce values in a controlled and asynchronous manner.

Here's an example of a generator-based asynchronous iterator:

Generator-based ayncronous iterator JavaScript

In this example, the `asyncNumberGenerator` produces values by awaiting an asynchronous function call.

Conclusion

Generators and iterators, especially when used together, provide a powerful toolset for managing asynchronous operations and creating more readable and maintainable asynchronous code.

As you continue your coding journey, embrace the world of JavaScript asynchronous patterns armed with the knowledge of generators and iterators. May your code flow smoothly, your applications remain responsive, and your development journey be filled with innovation and success.

Related articles: