Refactoring Callback Hell to Promise Land

Beginner JavaScript

Enjoy these notes? Want to Slam Dunk JavaScript?

These are notes based on my Beginner JavaScript Video Course. It's a fun, exercise heavy approach to learning Modern JavaScript from scratch.

Use the code BEGINNERJS for an extra $10 off.

BeginnerJavaScript.com

In this lesson we will go back to the event loop example, where we added the classes of circle, purple and fadeout, and we will refactor it to use promises instead.

callback hell in code

We will be coming back to this example one more time and refactor it after we learn async/await.

Take out event-loop.html file, duplicate it and rename it as promise-chain.html.

The first thing we want to do is make a function that will simply wait for a certain amount of time. This is something Wes does in almost every single project because it is such a common thing.

Make a function called wait which will take in the number of milliseconds we want to wait using the parametetr ms, which we will default to 0 seconds, and then we will return a new promise which will resolve after the number of milliseconds that got sent in.

Then we will just test our new method by waiting 2 seconds (2000 milliseconds) before logging "DONE".

function wait(ms = 0) {
  return new Promise(function(resolve) {
    setTimeout(resolve, ms);
  });
}

wait(200).then(() => {
  console.log('Done');
})

Now if you refresh the page, after 2 seconds you should see "done" logged to the console.

npm package waait

That is such a common thing that Wes actually has an npm package called waait which gets around 75k downloads per week and all it does is return a promise that resolves after a certain number of milliseconds that have been passed in.

We can use the implicit return and arrow function to refactor the wait function as shown below to be on one liner πŸ‘‡

const wait = (ms = 0) => new Promise(resolve => setTimeout(resolve, ms));

Now that we have this wait function, we can start to tackle all the callback hell.

Let's just rewrite it starting at the beginning by taking all our setTimeouts and instead making them promise based.

The first thing we will do is make an external function called animate and when someone clicks the go button, we will run animate.

animate will take in the event and let's start moving logic up to it.

For the event listener we already had on go, we will replace click with clickXXXXX so it doen't actually trigger and we can refactor piece by piece.

function animate(e) {

}

go.addEventListener('click', animate);

go.addEventListener('clickxxxx', function go(e) {

})

The first thing we want to do is grab the element, then we want to change the text to "GO" when clicked, which is immediately so we can put it right into our animate function.

function animate(e) {
  const el = e.currentTarget;

  // 1. change the text to GO when clicked
  el.textContent = 'GO';
}

Now we want to make it a circle after 2 seconds so we can use the wait function we made. We will chain a .then(), although there is no piece of data that is resolved from the wait, it simply is just done.

function animate(e) {
  const el = e.currentTarget;

  // 1. change the text to GO when clicked
  el.textContent = 'GO';

  wait(200).then(() => {
     el.classList.add('circle');
  })
}

Now after 2 milliseconds, the GO square will turn into a circle. If you refresh the page, you will see it still works.

after 2 milliseconds GO square turned into circle

Now how do we make it red after 0.5 seconds? We cannot just call it after the wait function because that will cause it to go red before it goes to circle.

What we can do instead is we can return another wait() of 500 milliseconds and then chain another .then() onto it and put our third item in there, as shown below.

function animate(e) {
  const el = e.currentTarget;

  // 1. change the text to GO when clicked
  el.textContent = 'GO';

  // 2. make it a circle after 2 seconds
  wait(200).then(() => {
     el.classList.add('circle');
     return wait(500);
  }).then(() => {
    // 3. make it red after 0.5 seconds
    el.classList.add('red');
  })
}

To make this more readable you can format them each on their own line like so πŸ‘‡

function animate(e) {
  const el = e.currentTarget;

  // 1. change the text to GO when clicked
  el.textContent = 'GO';

  // 2. make it a circle after 2 seconds
  wait(200)
    .then(() => {
      el.classList.add('circle');
      return wait(500);
    })
    .then(() => {
      // 3. make it red after 0.5 seconds
      el.classList.add('red');
    })
}

Now we have to make it a square after 0.25 seconds by removing the class of circle.

.then(() => {
  // 3. make it red after 0.5 seconds
  el.classList.add('red');
  return wait(250);
})
.then(() => {
  el.classList.remove('cirlce');
})

Now after 500 milliseconds we want to remove the red class and add the purple one like so πŸ‘‡

.then(() => {
  // 3. make it red after 0.5 seconds
  el.classList.add('red');
  return wait(250);
})
.then(() => {
  el.classList.remove('circle');
  return wait (500);
})
.then(() => {
  el.classList.remove('red');
  el.classList.add('purple');
})

Now finally, after half a second, we need to make it invisible.

.then(() => {
  el.classList.remove('red');
  el.classList.add('purple');
  return wait(500);
})
.then(() => {
  el.classList.add('fadeOut');
})

So what we have done here is taken the nested callback function and made it one level deep, which is waiting returning, waiting returning. That is chaining multiple .then()s onto each other.

That gets much better when we get to async/await but for now, this is the best way we can refactor these promises into a promise chain.

Find an issue with this post? Think you could clarify, update or add something?

All my posts are available to edit on Github. Any fix, little or small, is appreciated!

Edit on Github

Syntax Podcast

Hold on β€” I'm grabbin' the last one.

Listen Now β†’
Syntax Podcast

@wesbos Instant Grams

Master Gatsby

Master Gatsby

Building modern websites is tough. Preloading, routing, compression, critical CSS, caching, scaling and bundlers all make for blazing fast websites, but extra development and tooling get in the way.

Gatsby is a React.js framework that does it all for you. This course will teach you how to build your websites and let Gatsby take care of all the Hard Stuffβ„’.

MasterGatsby.com
I post videos on and code on

Wes Bos Β© 1999 β€” 2021