DEV Community

Cover image for Completely explaining JavaScript Promises, using money 💵
Bao Huynh Lam
Bao Huynh Lam

Posted on • Edited on

Completely explaining JavaScript Promises, using money 💵

What are Promises

JavaScript Promise is similar to how they mean in native English:

A promise/assurance that something will happen in the future, some value will return in the future, but not now

Imagine you are JavaScript, and your friend promises they will give you $5. You know your friend will give $5 sometime in the future, but not now, so you queue that task back into your memory, and continue doing your thing.

Why do we need Promises anyway?

Promise is part of the non-blocking asynchronous JavaScript workflow. Because JavaScript is single-threaded, we don't want certain actions to block the entire main thread. You don't want data fetching to block rendering the HTML. You don't want submitting requests to a server to pause the GIF playing in the background.

That's why JavaScript need a way to schedule some tasks that take time to the background and check back on them later, but not now. Prior to Promise, JavaScript uses callbacks, but that leads to huge problems of callback hell, so Promise is created as a more ergonomic solution.

Promises can be a chain

Imagine that immediately after your friend's promise, your mom demands $5 from you. Can you give it to her now? No you can't, because you don't have it, but you can schedule that: wait until after your friend give you $5, then you can give it to your mom. And that's how the Promises chain happens.

Friend gives you $5
  <-- Then you give it to mom
  <-- Then your mom praises you
  <-- Then you are happy
Enter fullscreen mode Exit fullscreen mode

Note that actions that depend on the value of a Promise get chained into a big Promise chain, and the whole thing becomes a big Promise. These scheduled things will happen sometime in the future, but not now. Once you are in Promise land, you can continue the chain, but can't go backout to the synchronous world.

Promises can have three states

Note that a Promise can have three states: Pending, Resolved, and Rejected. While waiting, the Promise is pending. If your friend's promise continues smoothly, the $5 is given successfully, then the Promise is resolved. However, if any errors happen in the Promise chain (your friend lost the money to the wind), it becomes Rejected, and will error out.

How to work with JS Promise & code samples

You can create a Promise yourself with the Promise constructor, which takes in a function that decide how to resolve or reject the promise.

const p = new Promise((resolve, reject) => {
  const today = new Date();
  // If at the time this Promise is invoked, it's Saturday
  if (today.getDay() === 6) {
    resolve("Happy day"); // Then successfully returns string
  } else {
    reject("Not Saturday") // Else error out
  }
})
Enter fullscreen mode Exit fullscreen mode

You can also quickly create a Promise that automatically resolves with Promise.resolve(), or a Promise that automatically rejects with Promise.reject(), which are mostly used for testing purposes.

Most of the time, you won't need to create a Promise yourself, and instead work with Promises given by other libraries like in fetch, axios, or bcrypt. You can chain any dependent action with .then(), and catch error with .catch()

// Schedule a Fetch promise
fetch("https://example.com")
  .then(value => document.title = value.toUpperCase())
  .catch(err => { console.error(err); document.title = "Error" } )
Enter fullscreen mode Exit fullscreen mode

Note that Promises are queued into the background by JavaScript to be checked back later, so the code below will not work like you expected, as you would be mismatching synchronous code and asynchronous code.

let title = undefined
// After promise is resolved, `value` will be assigned to `title`
// But for now, JS will queue this task onto the background, and
// continue immediately with the next line
newValuePromise().then(value => title = value)

// Therefore, even if you console.log here, you'd still get `undefined`,
// no matter how fast or instant your Promise is
// Once you are in Promise land, you can continue the chain, but
// can't go back out to the synchronous world.
console.log(title)
Enter fullscreen mode Exit fullscreen mode

One thing to watch out for is that only the value you return from the previous .then is available to the next .then. If you forgot to return any value, the next .then won't have it.

fetch("https://example.com")
  .then(value => {
    return value.toUpperCase()
  })
  .then(upperCaseVal => {
    console.log(upperCaseVal)
  })
  .then(value => {
    // Since the previous `.then` only console.log and did not
    // return any value, this `.then` does not have any value    
    console.log(value) // undefined
  })
Enter fullscreen mode Exit fullscreen mode

Async and Await

Async/Await is another syntax to work with Promises besides .then(). If you tag your function with async, then you use await instead of .then to continue the promise chain

async function handleResponse() {
  const response = await fetch("https://example.com");
  if (response === "happy") {
    // If happy response, then add a new diary entry
    const anotherResponse = await fetch("http://diary.com/new");
    console.log(anotherResponse);
    return anotherResponse;
  } else {
    return "normal day";
  }
}
Enter fullscreen mode Exit fullscreen mode

is equivalent to

function handleResponse() {
  fetch("https://example.com").then((response) => {
    if (response === "happy") {
      return fetch("http://diaray.com/new").then((anotherResponse) => {
        console.log(anotherResponse);
      });
    } else {
      return "Normal day";
    }
  });
}
Enter fullscreen mode Exit fullscreen mode

You can see how async/await really simplify the code flow and nesting, and is much more readable.

Note that the async function still returns a Promise. In an async function, it might look like you are writing normal synchronous JavaScript, but async/await is just a syntactic sugar over .then(), and whatever value you return from an async function will be wrapped into a Promise.

Some random caveats

setTimeOut() is actually not a Promise

You will notice that setTimeOut is used a lot in an asynchronous settings, either to stimulate Promise taking time in tutorials, or to schedule something that JS will come back to in the future. However, setTimeOut() actually does not return a Promise itself, and instead works with callback function (old-school style). You can convert setTimeOut() to a sleep() function that returns a Promise like below:

// Making setTimeOut return a resolved Promise after `ms` time
function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function test() {
  await sleep(1000);
  document.title = "Awoken after 1000ms sleep";
}

// Or without async
sleep(1000).then(() => document.title = "Changed")
Enter fullscreen mode Exit fullscreen mode

Top comments (0)