Understanding Promises in JavaScript

Promises are really confusing. There's a lot of keywords associated with promises and async JS:

  • Promise

  • resolve

  • reject

  • then

  • catch

  • async

  • await

From MDN:

new Promise(executor);

executor

A function that is passed with the arguments resolve and reject. The executor function is executed immediately by the Promise implementation, passing resolve and reject functions...

So if we break this down further, the expected arguments look like this:

new Promise(function executor(resolve, reject));

We define the executor function, which receives resolve and reject callback functions as arguments.

new Promise(function executor(resolve, reject) {
  if (true) {  // Success
    resolve('Resolved the promise');
  } else {  // Error
    reject('Rejected the promise');
  }
});

The executor function doesn't have to be named, I'm just making my example explicit.

Once the promise has been resolved or rejected, that will trigger either a chained then or catch to be called.

new Promise(function(resolve, reject) {
  if (true) {  // Success
    resolve('Resolved');
  } else {  // Error
    reject('Rejected');
  }
})
.then(function(result) {
  console.log(result);  // 'Resolved'
})
.catch(function(error) {
  console.log(error);  // 'Rejected'
});

An alternative syntax (that I think is more confusing), is to pass a second argument to then, which will handle reject in place of catch. I would not recommend this but it's good to know.

new Promise(function executor(resolve, reject) {
  if (true) {  // Success
    resolve('Resolved');
  } else {  // Error
    reject('Rejected');
  }
})
.then(function(result) {
  console.log(result);  // 'Resolved'
}, function(error) {
  console.log(error);  // 'Rejected'
});

Now that we understand the general behavior of Promises, let's break down this example sleep function.

function sleep(ms) {
  return new Promise(function(resolve) {
    setTimeout(function() {
      resolve('Resolved Value');
    }, ms);
  })
}

async function main() {
  console.log('Before sleep');
  const resolvedValue = await sleep(5000);  // 5 seconds
  console.log('After sleep');
  console.log(resolvedValue);  // 'Resolved Value'
}

main();

Normally if we were to call sleep without await, there would be no 5 second pause between the two log statements. That's because our sleep function returns a promise, so we must wait for it to be resolved if we want our main function to be executed synchronously.

In order to use the await keyword, the surrounding function, main, must be given the async keyword.

setTimeout is an asynchronous function, one of few in JS's built in library. However, setTimeout_ is not a Promise-based asynchronous function, it is callback-based_. In order to await for setTimeout to complete, we must wrap it in a promise, and resolve that promise inside the callback function we provide to setTimeout.

Here's a shorthand version of the above using arrow functions.

const sleep = ms => new Promise(resolve => setTimeout(() => resolve('Resolved Value'), ms));

async function main() {
  console.log('Before sleep');
  const resolvedValue = await sleep(5000);  // 5 seconds
  console.log('After sleep');
  console.log(resolvedValue);  // 'Resolved Value'
}

main();

Promises are confusing. If you don't understand them fully, don't worry about it. Check out other resources, and most importantly, play around with the code yourself.