Promises, Thenables, & Lazy-evaluation: What, Why, How
Tags: webdev, javascript, programming, tutorial
Published: 2023-01-23 16:21:27

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 `Promise`s to be more lazy…[JavaScript](https://austingil.com/category/javascript/) `Promise`s, that is.

It’ll make more sense in a moment.

{% embed https://www.youtube.com/watch?v=qj7diPVWTqY %}

First, let’s look at a basic [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/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`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) for the number of milliseconds that we should wait, then the Promise resolves with the value.

```javascript
/**
 * @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'"](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n0yhjoh9bb32xylaqz05.gif)

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`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) later on, rather than `await`ing the returned `Promise` right away.

```javascript
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'"](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v4h7p5i0y0y43p4ewz3t.gif)

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, `Promise`s 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](https://en.wikipedia.org/wiki/Lazy_evaluation) to only instantiate when you need it.

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

`Promise`s are not the only things that can be `await`ed 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('🙃') }" followed by, "🙃".](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8lhys8zogcinusp952wr.png)

This is kind of weird, but it also allows us to create different objects that **look** like `Promise`s, but aren’t. These objects are sometimes called “[Thenables](https://www.golinuxcloud.com/javascript-thenable/)“.

With that in mind, let’s create a new [class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) 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`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof).

```javascript
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.

```javascript
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'"](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f0nyfhpv2lsz9shbdol1.gif)

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 [ORM](https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping)s and query builders like [Knex.js](https://knexjs.org/) or [Prisma](https://www.prisma.io/).

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

```javascript
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`.

```javascript
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 `Promise`s 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](https://twitter.com/share?via=heyAustinGil). It's one of the best ways to support me. You can also [sign up for my newsletter](https://austingil.com/newsletter/) or [follow me on Twitter](https://twitter.com/heyAustinGil) if you want to know when new articles are published.

---

*Originally published on* [*austingil.com*](http://austingil.com)*.*