JavaScript Promises

Regina Furness
6 min readSep 17, 2020

--

The last few months I have been learning JavaScript and React.js while finishing the final stretch of the Software Engineering curriculum at Flatiron School. Using the Fetch API has been integral part of this experience. While the curriculum does explain how to use fetch() and promises, to some detail, I realized that there was a lot to the Fetch API and promises that I still didn’t understand. I wanted to understand the tools I had been using a little better. This blog will be about promises, and I will make a future blog post about the Fetch API.

Asynchronous Operations

JavaScript is single threaded. It can only do one thing at a time. In a synchronous operation, each part of the code waits until the first part is complete to run. In an asynchronous operation, you can write code that will be executed after the rest of the code has finished running. This code in example:

function asyncLog(){
//the first argument of setTimeout() is a callback function
//the second argument is the amount of milliseconds to wait
//before adding the callback function to the call stack.
setTimeout(()=>{console.log('3')}, 2000)

//Because it is only added to the call stack if it is empty
//it is worth keeping in mind that that is the MINIMUM amount
//of time that will pass before the code runs.
}
asyncLog()
console.log('1')
console.log('2')

Will console.log 1, 2, then 3. This is because even though asyncLog() is invoked first, setTimeout() is asynchronous, it will only add ()=>{console.log('3') to the call stack when it is empty.

(Spoiler: fetch() is asynchronous, too!)

Promises

What is a Promise? From MDN:

The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.

A promise has 3 states: pending, fulfilled, and rejected. The promise represents a value that is not known yet. At first the state of the promise is pending, then the promise will either be fulfilled/resolved or rejected, at which point the promise is considered settled. There are a few things to note here:

  • Once a promise is settled (either fulfilled or rejected) it cannot transition into another state. It is immutable.
  • A settled promise must return a value, even if it is undefined.
  • A promise is thenable.

What does that last bullet point mean? A promise supplies a .then() method. .then() Allows us to attach callbacks to the returned promise object, rather than having to pass them in, it also returns a promise, so it is chainable. You also can (and it is highly recommended to) chain a .catch() for handing errors and rejections. The callback(s) supplied to .then() will only run after the completion of the first asynchronous operation. This predictability is one of the biggest benefits to using promises to handle asynchronous operations.

Using Promises

So we’ve gone over some things about promises, let’s see some code to construct our own. Pay attention to where we place the console.log's.

// we are defining a function that returns a promise
function firstLetterA(word){
// the Promise constructor
// a promise takes two optional arguments a resolve callback
and a reject callback
return new Promise((resolve, reject) => {
console.log("The first promise is pending")
// if the first letter exists and it is equal to 'a'
if(!!word[0] && word[0].toLowerCase() === 'a'){
console.log("First Promise will be fulfilled") // fulfill the promise and return the word
resolve(word)
} else { // reject the promise with message that the word
// doesn't begin with 'a'!
reject(`${word} doesn't begin with a!`)
}
})
}
//a second similar function that checks if the second letter is 'b'function secondLetterB(word){
return new Promise((resolve, reject) => {
console.log("The second promise is pending") if(!!word[1] && word[1].toLowerCase() === 'b'){ //note the promise is NOT fulfilled yet, but it will be
console.log("The second promise will be fulfilled")
resolve(word) } else { reject(`${word}'s second letter isn't b!`)
}
})
}
// a function we are going to call as a rejection callbackfunction thereWasARejection(){
console.log('there was a rejection!')
return 'failed'
}

Now we have some functions that return promises defined, let’s do some chaining!

firstLetterA("Abby")
.then(secondLetterB)
.then(word => console.log(`${word} begins with a and b`))
.catch(errors => console.log(errors))
console.log("logging outside of the promise chain")
//this is what logs from the above chain
=> The first promise is pending
=> First Promise will be fulfilled
=> logging outside of the promise chain
=> The second promise is pending
=> The second promise will be fulfilled
=> Abby begins with a and b
firstLetterA("And")
.then(secondLetterB)
.then(word => console.log(`${word} begins with a and b`))
.catch(error => console.log(error))
console.log("logging outside of the promise chain")
// this is what logs from this chain
=> The first promise is pending
=> First Promise will be fulfilled
=> logging outside of the promise chain
=> The second promise is pending
=> And's second letter isn't b!
firstLetterA("Obtuse")
.then(secondLetterB)
.then(word => console.log(`${word} begins with a and b`))
.catch(error => console.log(error))
console.log("logging outside of the promise chain")
// and this is what logs from the above
=> The first promise is pending
=> logging outside of the promise chain
=> Obtuse doesn't begin with a!
// now let's see what happens when we supply a rejection callback to one the second .then()
firstLetterA("Obtuse")
.then(secondLetterB, thereWasARejection)
.then(word => console.log(`${word} begins with a and b`))
.catch(error => console.log(error))
console.log("logging outside of the promise chain")
=> The first promise is pending
=> there was a rejection!
=> failed begins with a and b

As you can see from the last two examples if you supply a rejection callback, and use another .then() the return value from the rejection callback will be what gets passed in, and the .catch() will not run. If you don’t supply a rejection callback, it will skip any consecutive .then()’s and look for a .catch(). It is also worth noting that you can supply a .then() after .catch() that will run whether or not any of the promises are fulfilled or rejected. In the second .then() we are supplying the resolve callback inline, which means that it is still returning a promise, we could continue to chain .then()'s, the return value of that inline callback will be what is passed in. You can see from the order of the console.log's that Promises are asynchronous, the code outside of the .then() chain will be run first, the code in each consecutive .then() will wait on the first to be settled before running. Again, this is important because it makes asynchronous code predictable. You know your code won’t be run until the promise is settled. There is another instance method that can be run in the promise chain called .finally(). This method returns a promise, it will be run when the promise is settled, whether it is fulfilled or rejected. It’s purpose is mainly eliminating duplicate code in .then() and .catch().

Promise Static Methods

Promises have a handful of static methods as well. They are:

Promise.all() takes in an array of promises, it waits for them all to be settled. It returns with either an array of all of the values from the resolved promises, or the reason of the first promise that was rejected.

Promise.allSettled() also takes an array of promises and waits for them all to be settled. Unlike Promise.all() it returns a promise with the value of an array of objects with that describe the outcome of each promise.

Promise.any() takes an array of promises, returns a promise with the value from the promise that was fulfilled first. If no promise was fulfilled it returns an error that all promises were rejected.

Promise.race() takes an array of promises. Returns a new promise with either the value or reason for the first promise that was fulfilled or rejected.

Promise.reject() creates a new promise that is rejected with the reason supplied as the argument.

Promise.resolve() creates a new promise that is resolved with the value supplied as the argument. Useful if you need to work with a promise but are unsure whether or not the value you have is one.

Let’s Resolve This

Promises totally changed the way asynchronous code was handled in JavaScript. They allow asynchronous code to be more predictable, easier to write, and understand, and make it easy to chain asynchronous operations. Promises are an essential building block of the Fetch API, and understanding them is crucial to glean any deeper understanding of .fetch(). Hopefully after reading this you have a better understanding of promises and their usefulness.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Regina Furness
Regina Furness

No responses yet

Write a response