Deferred; Promise with Resolvers

Promise.withResolvers is a new JavaScript spec that’s landing in a runtime near you soon if it hasn’t already.

How is it useful? For example, let’s write an asynchronous function that returns your public IP address. We could return a Promise the old way:

const getAddress = () => new Promise(resolve => {
  fetch('https://icanhazip.com').then((response) => {
    response.text().then((text) => {
      resolve(text);
    });
  });
});
getAddress().then((ipAddress) => {
  console.log(ipAddress);
});

If you have experience with Promise you’ll know how quickly chaining them leads to nested code that’s ugly and verbose.

Using async/await syntax is so much cleaner:

const getAddress = async () => {
  const response = await fetch('https://icanhazip.com');
  return await response.text();
}
const ipAddress = await getAddress();
console.log(ipAddress);

So clean! Now let’s add the new hotness withResolvers:

const getAddress = () => {
  const {promise, resolve} = Promise.withResolvers();
  fetch('https://icanhazip.com').then(async (response) => {
    resolve(await response.text());
  });
  return promise;
}
const ipAddress = await getAddress();
console.log(ipAddress);

It may not be immediately obvious but promise is returned before fetch is complete.

So how exactly is this useful? Let’s build a file downloader with a cache using deferred promises. I’m using my Turtle demo to throttle a 10KB file download to make the async process more obvious.

const cache = new Map();

const fetchBlob = async (url, resolve) => {
  const response = await fetch(url);
  const body = await response.blob();
  resolve(body);
}

const download = (url) => {
  if (cache.has(url)) return cache.get(url);
  const {promise, resolve} = Promise.withResolvers();
  cache.set(url, promise);
  fetchBlob(url, resolve);
  return promise;
}

const url = 'https://turtle.deno.dev/bin';
const p1 = download(url);
const p2 = download(url);

console.log(await p1 === await p2);

In this example the logged result is true because both calls to download return the same promise. The fetch is only run once and download does not wait for fetchBlob to finish.

This is useful! We’re able to pass around the promise, resolve (and reject) separately. The withResolvers spec exists because the deferred promise pattern was “frequently re-written by developers”. The spec is now at stage 4 meaning it’s ready and shipping.

Buy me a coffee! Support me on Ko-fi