Dan Levy's Avatar DanLevy.net

Stop trying to make async/await happen

Promises are so fetch right now

Stop trying to make async/await happen

Since the beginning of time, developers have fought many silly fights. From the classic “Tabs vs. Spaces” to the timeless “Mac vs. PC” debate, we’re good at finding distracting arguments.
Answers: Linux & Spaces.

The Fight…?

Promises vs. Async/Await!

Wait, is this a fight? It must be right? We don’t seem to talk about callbacks anymore?

No, it’s not a fight. Ultimately it’s another potential tool in your toolbox. However, because async/await doesn’t replace all Promise functionality (specifically Promise.all, .race) it’s misleading presenting it as a replacement.

There’s a lot of influential people promoting this misconception async/await is the Promises replacement everyone’s been waiting for.

Hint: No, nope, and not even a little.

A recent addition to VS Code advances this bias. As @umaar tweeted:

Visual Studio Code can now convert your long chains of Promise.then()'s into async/await! 🎊 Works very well in both JavaScript and TypeScript files. .catch() is also correctly converted to try/catch ✅ pic.twitter.com/xb39Lsp84V

— Umar Hansa (@umaar) September 28, 2018

If you hate Promises, and want this refactoring feature, I don’t blame you.
I empathize. I understand.
I’ve been there. 🤗


I used to hate Promises. Today, I have come back around completely. Promises are amazing. They can enable/encourage you to take advantage of function composition.

There are 2 areas I recommend focusing on first to advance your Promise technique.

  1. Named functions (no anonymous)
  2. Single-purpose functions

#1: Named Functions!

Kill your anonymous methods. Using named functions makes code read like poetry of your requirements.

Let’s look at a common example:

Making an HTTP GET request using fetch:

Anti-Pattern

// ❌ Using anonymous inline functions 💩
fetch(url)
  .then(response => response.status < 400
    ? response
    : Promise.reject(new Error('Request Failed: ' + response.ststus)))
  .then(response => response.text())

Solution: Named Methods

// ✅ Clarity emerges: named functions
fetch(url)
  .then(checkResponse)
  .then(getText)


// Reusable general-purpose functions
function checkResponse(response) {
  return response.status < 400
    ? response
    : Promise.reject(new Error('Request Failed: ' + response.ststus))
}
function getText(response) {
  return response.text()
}

The benefits of this approach are increasingly apparent as you get DRY-er code.

Additional Resources: Check out my 1 minute videos of basic logging and advanced debugging using this technique.

#2: Single Purpose (Functions)

It sounds deceptively precise: Single Purpose.

Yet it’s so subjective, arbitrary, and sure, sometimes even meaningless.

Interestingly, most developers report they are pretty dang good at Single Purpose’ing their code. Not unrelated: they report being great drivers too!

Let’s look at an example the (incredibly talented) Jake Archibald features in his async/await article for the Google Developers site.

// source: https://developers.google.com/web/fundamentals/primers/async-functions
function logInOrder(urls) {
  // fetch all the URLs
  const textPromises = urls.map(url => {
    return fetch(url).then(response => response.text());
  });

  // log them in order
  textPromises.reduce((chain, textPromise) => {
    return chain.then(() => textPromise)
      .then(text => console.log(text));
  }, Promise.resolve());
}

Single Purpose?

I’d say no. What’s logInOrder doing?

  1. loop through a list of urls
  2. apply them to an inline HTTP GET:
  3. HTTP fetch
  4. return response text body
  5. append a .then(text => console.log(text)) after each promise in textPromise
  6. print results serially

There are 5 anonymous methods defined in this single function. As Jake even points out, the .reduce is too complex. It doesn’t make sense to hand-write nuanced mechanisms all over your code. Put another way, we don’t write DOM creation code with endless document.createElement(), element.setAttribute(), etc. Instead we choose the best tool out of many options: helper/utility functions, libraries or frameworks.

Solution: Single Purpose Functions

Begin by extracting methods

Continue by replacing the .reduce() and logPromise() with a Promise.all and a ..map()

Summary

Try apply these techniques to your own code! Then tweet at me & let me know how it went. Or if you have questions or comments, reach out as well!

Help spread the #PromiseTruth & share this article. ❤️

credit: matt-nelson-414464-unsplash.webp

Edit on GitHubGitHub