DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
  1. DZone
  2. Coding
  3. JavaScript
  4. Promises, Thenables, and Lazy-Evaluation: What, Why, How

Promises, Thenables, and Lazy-Evaluation: What, Why, How

JavaScript Promises evaluate eagerly, but sometimes that's a problem. This post covers why and how to create custom lazy-evaluating promises.

Austin Gil user avatar by
Austin Gil
CORE ·
Jan. 23, 23 · Tutorial
Like (2)
Save
Tweet
Share
3.53K Views

Join the DZone community and get the full member experience.

Join For Free

It’s the start of a new year, and while lots of folks are promising to be more active, I’m going to show you how to make Promises to be lazier: JavaScript Promises, that is.

It will make more sense in a moment.

First, let’s look at a basic Promise example. Here I have a function called sleep that takes a time in milliseconds and a value. It returns a promise that will execute a setTimeout for the number of milliseconds that we should wait, then the Promise resolves with the value.

/**
 * @template ValueType
 * @param {number} ms
 * @param {ValueType} value
 * @returns {Promise<ValueType>}
 */
function sleep(ms, value) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(value), ms);
  });
}


It works like this:

JavaScript console with the code, "await sleep(1000, 'Yawn & stretch')". Then after one second, "'Yawn & stretch'"


We can await the sleep function with the arguments 1000 and 'Yawn & stretch', and after one second the console will log the string, ‘Yawn & stretch’.

There’s nothing too special about that. It probably behaves as you would expect, but it gets a little weird if we store it as a variable to await later on, rather than awaiting the returned Promise right away.

const nap = sleep(1000, 'Yawn & stretch')


Now let’s say we do some other work that takes time (like typing out the next example), and then await the nap variable.

Typing into the JavaScript console, "await nap" and immediately seeing the response "'Yawn & stretch'"


You might expect a one-second delay before resolving, but in fact, it resolves immediately. Anytime you create a Promise, you instantiate whatever asynchronous functionality it’s responsible for.

In our example, the moment we define the nap variable, the Promise gets created which executes the setTimeout. Because I’m a slow typer, the Promise will be resolved by the time we await it.

In other words, Promises are eager. They do not wait for you to await them.

In some cases, this is a good thing. In other cases, it could lead to unnecessary resource use. For those scenarios, you may want something that looks like a Promise, but uses lazy evaluation to only instantiate when you need it.

Before we continue, I want to show you something interesting.

Promises are not the only things that can be awaited in JavaScript. If we create a plain Object with a .then() method, we can actually await that object just like any Promise.

JavaScript console with the text, "await { then: () => console.log('upside-down smiley emoji') }" followed by, upside-down smiley emoji.


This is kind of weird, but it also allows us to create different objects that look like Promises, but aren’t. These objects are sometimes called “Thenables."

With that in mind, let’s create a new class called LazyPromise that extends the built-in Promise constructor. Extending Promise isn’t strictly necessary, but it makes it appear more similar to a Promise using things like instanceof.

class LazyPromise extends Promise {
  /** @param {ConstructorParameters<PromiseConstructor>[0]} executor */
  constructor(executor) {
    super(executor);
    if (typeof executor !== 'function') {
      throw new TypeError(`LazyPromise executor is not a function`);
    }
    this._executor = executor;
  }
  then() {
    this.promise = this.promise || new Promise(this._executor);
    return this.promise.then.apply(this.promise, arguments);
  }
}


The part to focus on is the then() method. It hijacks the default behavior of a standard Promise to wait until the .then() method is executed before creating a real Promise. This avoids instantiating the asynchronous functionality until you actually call for it, and it works whether you explicitly call .then() or use await.

Now, let’s see what happens if we replace the Promise in the original sleep function with a LazyPromise. Once again, we’ll assign the result to a nap variable.

function sleep(ms, value) {
  return new LazyPromise((resolve) => {
    setTimeout(() => resolve(value), ms);
  });
}

const nap = sleep(1000, 'Yawn & stretch')


Then we take our time to type out the await nap line and execute it.

Typing into the JavaScript console, "await nap" and after one second delay, seeing the response "'Yawn & stretch'"


This time, we see a one-second delay before the Promise resolves, regardless of how much time passed since the variable was created. (Note that this implementation only creates the new Promise once and references it in subsequent calls. So if we were to await it again, it would resolve immediately like any normal Promise.)

Of course, this is a trivial example that you probably won’t find in production code, but there are many projects that use lazy-evaluated Promise-like objects. Probably the most common example is with database ORMs and query builders like Knex.js or Prisma.

Consider the pseudo-code below. It’s inspired by some of these query builders:

const query = db('user')
  .select('name')
  .limit(10)

const users = await query


We create a database query that goes to the "user" table and selects the first ten entries and returns their names. In theory, this would work fine with a regular Promise.

But what if we wanted to modify the query based on certain conditions like query string parameters? It would be nice to be able to continue modifying the query before ultimately awaiting the Promise.

const query = db('user')
  .select('name')
  .limit(10)

if (orderBy) {
  query.orderBy(orderBy)
}
if (limit) {
  query.limit(limit)
}
if (id) {
  query.where({ id: id })
}

const users = await query


If the original database query was a standard Promise, it would eagerly instantiate the query as soon as we assigned the variable, and we wouldn’t be able to modify it later on. With lazy evaluation, we can write code like this that’s easier to follow, improves the developer experience, and only executes the query once when we need it.

That’s one example where lazy evaluation is great. It might also be useful for things like building, modifying, and orchestrating HTTP requests.

Lazy Promises are very cool for the right use cases, but that’s not to say that they should replace every Promise. In some cases, it’s beneficial to instantiate eagerly and have the response ready as soon as possible. This is another one of those “it depends” scenarios. But the next time someone asks you to make a Promise, consider being lazy about it ( ͡° ͜ʖ ͡°).

Thank you so much for reading. If you liked this article, please share it. 

Evaluation JavaScript

Published at DZone with permission of Austin Gil. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • 3 Main Pillars in ReactJS
  • Testing Repository Adapters With Hexagonal Architecture
  • Introduction to Spring Cloud Kubernetes
  • OpenVPN With Radius and Multi-Factor Authentication

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: