Type something to search...
Understanding JavaScript Promises and Lazy Loading Callbacks

Understanding JavaScript Promises and Lazy Loading Callbacks

In JavaScript, thenables play a key role in asynchronous programming, particularly with Promises in ES6. One of the advantages of ES6 Promises (which use thenables) over older implementations like jQuery’s deferred objects is the ability to lazy load callback functions.

Thenables in ES6

As mentioned earlier, a thenable is any object that has a then method, making it “promise-like.” ES6 Promises, introduced in ECMAScript 2015 (ES6), are a formalization of the thenable concept and provide a built-in way to manage asynchronous operations. They have precise rules for managing asynchronous code, including lazy evaluation of callbacks.

A key feature of ES6 Promises is that they don’t immediately execute the callback functions passed to the then method. Instead, the callback is only queued to execute once the promise resolves or rejects. This is referred to as lazy loading the callback functions.

Here’s how that works in practice:

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => resolve('ES6 Promise resolved!'), 1000);
});

myPromise.then(result => {
  console.log(result);  // Outputs "ES6 Promise resolved!" after 1 second
});

In this example, the then method does not execute immediately. Instead, it waits until the promise is resolved (after 1 second) before running the callback function.

Lazy Loading Callbacks in ES6 Promises

The concept of lazy loading in ES6 Promises is an important shift from earlier asynchronous implementations. The idea is that the promise itself can start immediately (when the executor function is invoked), but the callback functions passed into then() or catch() are only triggered when the promise resolves or rejects.

This means that the code passed into then() is not run until absolutely necessary, allowing efficient management of asynchronous flows and preventing unnecessary execution of code.

For example:

const promise = new Promise((resolve, reject) => {
  console.log("Promise started");
  setTimeout(() => resolve('Done'), 2000);
});

promise.then(() => console.log("This runs when resolved"));  // Lazy-loaded
console.log("Code after promise");

This outputs:

Promise started
Code after promise
This runs when resolved  // after 2 seconds

Here, the callback inside then() is not executed immediately but is “lazy-loaded” — it waits for the promise to resolve before running. The asynchronous operation (the setTimeout) is initiated immediately, but the code passed into then() doesn’t run until the result is available. This lazy loading improves both clarity and efficiency when managing async tasks.

jQuery Deferred Objects vs ES6 Thenables

Before ES6, libraries like jQuery implemented their own versions of thenables via Deferred objects. While jQuery’s Deferred objects offered a way to handle asynchronous actions and chaining, there was no built-in mechanism for lazy loading the callbacks. This meant that callbacks could sometimes be eagerly evaluated or executed immediately based on how the asynchronous chain was set up.

Consider this example with jQuery Deferred:

const deferred = $.Deferred();

deferred.done(() => console.log('jQuery Deferred resolved'));
deferred.resolve();

Unlike ES6 Promises, jQuery’s .done() method executes the callback immediately if the deferred object is already resolved when the callback is added. This can lead to situations where callbacks are executed unexpectedly as soon as they’re attached, making it harder to reason about the exact timing of execution.

In contrast, ES6 Promises don’t exhibit this behavior. Even if a promise is already resolved, the callback passed to then() will always be added to the job queue and run asynchronously after the current JavaScript execution cycle, ensuring consistent and predictable execution:

const promise = Promise.resolve("Already resolved");
promise.then(() => console.log('ES6 promise handled'));  // This will be lazy-loaded and run after current cycle
console.log("This runs first");  // Even though the promise is resolved, this will log first

This outputs:

This runs first
ES6 promise handled

Benefits of Lazy Loading with Thenables

Lazy loading of callbacks in ES6 Promises offers several key advantages:

  1. Consistent Asynchronous Behavior: The behavior of ES6 Promises is always consistent, regardless of when you attach your then or catch callbacks. Even if a promise is resolved before attaching a callback, the function will still be deferred until the next microtask queue is processed.

  2. Improved Performance: Lazy loading ensures that unnecessary operations are avoided, as the callbacks only run when needed — that is, when the promise settles (resolves or rejects). This can lead to more efficient resource usage in large-scale asynchronous applications.

  3. Predictable Execution: With lazy evaluation, developers can reason about their code execution more easily, ensuring that asynchronous actions happen only after the necessary events (like resolution or rejection of a promise) occur.

  4. Interoperability: Since any object with a then method can be treated like a promise, libraries can still offer custom implementations or lightweight thenables while benefiting from the lazy loading of callbacks, as long as they follow the thenable pattern.

Conclusion

ES6 thenables revolutionized how JavaScript handles asynchronous operations, providing developers with a lazy-loaded callback mechanism that ensures consistency, performance, and predictability. Unlike older implementations like jQuery’s Deferred objects, ES6 Promises ensure that callbacks are deferred until absolutely necessary, which can make asynchronous workflows both easier to manage and more efficient.

Understanding the difference between eager and lazy evaluation in async code is essential for writing clean, maintainable JavaScript, and ES6 thenables provide a robust solution for this challenge.

Last Understanding JavaScript Promises and Lazy Loading Callbacks

Share :

Related Posts

Horizontal Scaling in Kubernetes

Horizontal Scaling in Kubernetes

Horizontal scaling in Kubernetes refers to dynamically adjusting the number of application instances (pods) based on workload changes to maintain optimal performance. Unlike vertical scaling, which in

read more
How to Use Docker for Development Environments

How to Use Docker for Development Environments

When developing an application running in Docker, you can edit the files on your local machine and have those changes immediately reflected in the running container. This is typically done using Docke

read more
CLI pager commands - more, less, and most

CLI pager commands - more, less, and most

These PAGER commands allow you to navigate through file and data stream content with a variety of useful commands. If you need to manually visually navigate through a lot of text or data, you'll f

read more
Defining New ASCII Designs For Thomas Jensens Boxes Software

Defining New ASCII Designs For Thomas Jensens Boxes Software

The "Boxes" command line tool takes a block of text and wraps it in one of 50 some frames listed with boxed -l and specified by the user with boxes -d the text can either be piped into boxed or a

read more
Javascript ES6 Modules, Introduction

Javascript ES6 Modules, Introduction

With the release of ECMAScript 2015 (ES6), JavaScript introduced a powerful new feature: modules. This addition was a significant shift in how developers structure and manage code, allowing for better

read more
Understanding JavaScript Promises

Understanding JavaScript Promises

In JavaScript, the concept of thenables often arises when working with Promises. Promises inherit from the base class Thenable, meaning that Promises are a type of Thenable, but a Thenable is not

read more
Part 4, Dynamic Imports and Lazy Loading

Part 4, Dynamic Imports and Lazy Loading

Introduction So far, we’ve explored the world of static imports in JavaScript, where dependencies are imported at the start of a script’s execution. However, in modern web development, there are c

read more
Part 2, Understanding Named and Default Exports

Part 2, Understanding Named and Default Exports

Introduction In the previous part, we introduced the basics of importing and exporting in JavaScript ES6, covering both named and default exports. Now, it’s time to explore these t

read more
Part 1, Getting Started with Modules

Part 1, Getting Started with Modules

Introduction Before ES6, JavaScript did not have a native module system, which made it difficult to split large codebases into manageable pieces. Developers relied on patterns like the Module Patt

read more
Part 3, Re-exports and Module Aggregation

Part 3, Re-exports and Module Aggregation

Introduction As projects grow, the number of modules and dependencies can quickly become overwhelming. In large codebases, managing and organizing these modules is key to maintaining readability a

read more
Managing Multiple Git Identities Per Single User Account

Managing Multiple Git Identities Per Single User Account

If you need to work make changes to code under different identities, there are a few different ways you can approach this. The first solution I saw on many webpages was way too clunky for my taste. It

read more
Secure Authentication & Authorization Exercises

Secure Authentication & Authorization Exercises

Each exercise includes:Scenario Initial Information Problem Statement Tasks for the student Bonus Challenges for deeper thinking**Section 1: OAuth 2.0 + PKCE

read more
Never Been a Huge Fan of IDEs, but I Like Visual Studio Code

Never Been a Huge Fan of IDEs, but I Like Visual Studio Code

To be completely honest, for the past many years, I've debated whether or not to use an IDE. On one had, they provide a number of features like code completion, debugging, and a number of other things

read more
Powerful Text Selection Operations in VSCode

Powerful Text Selection Operations in VSCode

VSCode has become one of the most popular IDEs in recent years. It is also available for free. Here are a few text selection options of which you may not be aware. Multiple Selections It is possi

read more
Visual Studio Code - Creating a Custom Text Filter Extension

Visual Studio Code - Creating a Custom Text Filter Extension

In this post I will describe a way to create an extension which allows the user to receive the selected text as a string passed into a Typescript function, run that string through any command line pro

read more
What is Docker and Where and Why Should You Use it?

What is Docker and Where and Why Should You Use it?

Docker is a platform designed for containerization, allowing developers to package applications and their dependencies into lightweight, portable containers. These containers are isolated environments

read more
What is Kubernetes? Where and Why Should You Use it?

What is Kubernetes? Where and Why Should You Use it?

Key Use Cases and Benefits Kubernetes simplifies the deployment and scaling of applications through automation. It facilitates automated rollouts and rollbacks, ensuring seamless updates without d

read more
Secrets Management DevOps Tools and More

Secrets Management DevOps Tools and More

These tools provide a means of securely storing secrets (encryption keys, passwords, all that good stuff that you want to make available to your production systems, but you must protect from exposure)

read more
Web Application Boilerplate

Web Application Boilerplate

I've been tinkering with a number of projects and along the way I've come up with what I think is a solid starting point for any web application that you might build. Understanding that your applicat

read more
Part 5, Best Practices and Advanced Techniques

Part 5, Best Practices and Advanced Techniques

In the previous parts of this series, we explored the fundamentals of module importing and exporting in ES6, the different ways to define modules, and how to work with default and named exports. In th

read more
Using Makefiles, SOPS, and virtualenv Together for Elegant Python Environments

Using Makefiles, SOPS, and virtualenv Together for Elegant Python Environments

I've been managing my secrets with sops ever since I looked into the subject last month, and I've been using Makefiles to handle bringing up my docker environments as they provide a nice way to not

read more