Yuri Lavrukhin

Asynchronous JS. Promises By Example. Part 1

πŸ‘‹ Introduction

Hi! In this article, you'll learn how promises works, main concepts, syntax, how to use it to fetchΒ data, handleΒ errors, nuances, and tricks of working with them. (with examples!)

In JavaScript native promises appeared in 2015 with ES6 (or ECMAScript2015) standard and become one of the most important building blocks for asynchronous operations.

Promise's purpose β€” is to let us write asynchronous code conveniently.

Before we dive in. If you are not familiar with promises I recommend you to open the browser's console and execute code examples in the article while reading to get a better understanding and feel the code.

πŸ“ Main Concepts

Asynchronous operations β€” is a JavaScript instructions that executes asynchronously, in other words β€” it takes some time for them to be completed and return a value, or failed and return an error (they fail often).

Promise can help us to handle these operations. It's a special JavaScript object, that has state and stores value.

The promise can be returned synchronously from the asynchronous function.

For example: ↓

// fetch function returns a promise
const promiseResponse = fetch('https://jsonplaceholder.typicode.com/posts/1')
console.log(promiseResponse) // Pending Promise (why pending β†’ below)

Pending promise in the console

Pending promise in the console

🀞 3 Possible Promise States

A bit theory for further more productive practice.

As I said before, the promise is a special JavaScript object, and this object has the state.

3 possible promise's states

Promise's possible states

Once the promise is no longer pending (e.g. fulfilled or rejected), we say that promise has been settled.

So, settled is not a special state, it's just a word to say that promise has settled with some state β€” fulfilled or rejected

Once the promise settled, it can no longer change its state.

There are new states we've learned β€” fulfilled and rejected. Let's take a look at them in the next section.

πŸ‘‰ then Method

When our promise fulfilled or rejected β€” we can access the value from async operation.

But we can't do it synchronously 😐

There is where the then method comes in.

promise.then(
	onFulfilled?: Function,
	onRejected?: Function
) => Promise

then signature

then sets onFulfilled and onRejected functions as handlers for our promise.

These functions receive a value from async operation as a first argument.

When async operation done β€” promise becomes fulfilled and first function will call.

const promiseResponse = fetch('https://jsonplaceholder.typicode.com/posts/1')
promiseResponse.then(
    (value) => console.log(value, promiseResponse),
    (errorValue) => console.log(errorValue, promiseResponse)
  )

Promise fulfilled

Promise fulfilled in console

You might notice that browser call our fulfilled promise's state β€” resolved. You can think of it as fulfilled

But if we made a typo in our address...

Promise will become rejected and second function will call.

const promiseResponse = fetch('https://HTMLplaceholder.typicode.com/posts/1')
promiseResponse.then(
    (value) => console.log(value, promiseResponse),
    (errorValue) => console.log(errorValue, promiseResponse)
  )

Promise rejected

Promise rejected in console

Note:

then returns the value that we returned from onFulfilled or onRejected function wrapepped into fulfilled promise.

But if we return the promise β€” then returns this promise.

If there is no handler for some promise state in thenΒ β€” then just will return promise on which this then method was called. For example: an error happened and there's no onRejected handler. In this case β€” then returns this rejected promise as it is.

πŸ“¦ Getting Data With fetch

We want to get the response's body, so we need to parse it using the json method. This is also an async function that returns a promise.

const promiseResponse = fetch('https://jsonplaceholder.typicode.com/posts/1')
promiseResponse.then(response => console.log(response.json())) // log pending promise

We already know how to deal with pending promises. Just add then to it

const promiseResponse = fetch('https://jsonplaceholder.typicode.com/posts/1')
const promiseData = promiseResponse.then((response) => response.json())
promiseData.then((data) => console.log(data))

Get async data

Get async data

Finally got the actual data in the console. πŸ₯³

We can replace the last line to ↓ (try it yourself!)

promiseData.then(console.log)

because console.log is a regular JavaScript function (like the ones we write in our code) that gets infinite number of arguments and log them to the console. In our case there is only one argument and works nicely.

⚠️ Error Handling

Because asynchronous operations fail all the time.

We already handled an error in then method section

We can handle errors with then(null, onRejected) and catch(onRejected).

catch(onRejected) is equivalent and shorthand for then(null, onRejected).

const promiseResponse = fetch('https://HTMLplaceholder.typicode.com/posts/1') // TYPO!
const promiseData = promiseResponse.then((response) => response.json())
const data = promiseData.then(console.log)
data.then(null, (errorValue) => console.log(errorValue, 'catched here!'))

catched error in promise

Try to replace then(null, onRejected) with catch(onRejected).

Note: we write onRejected handler on the last promise to handle all possible errors that can happened from the first promise to the last.

All right! Let's talk about fetching data as most common async operation case. What if our operation done successfully, but server responded with error? In promise terms, our async operation done well and promise will be fulfilled. Let's learn how we can handle this!

const promiseResponse = fetch('https://jsonplaceholder.typicode.com/posts/99999999')
// server responds with 404 error
const promiseData = promiseResponse
  .then((response) => {
      if (!response.ok) throw Error('whoops!')
      response.json()
    })
const data = promiseData.then(console.log)
data.then(null, (errorValue) => console.log(errorValue, 'catched here!'))
// ... rewrite last line with catch

Here we check ok flag. As we already know, then returns what we return from handler function

Throwing something synchronously (in this case β€” Error) is the same as if we would returned a rejected promise.

By the way, we can create one already rejected with Promise.reject(). Behaviour will be the same. Try this! And don't forget to return it!

β›“ Promise Chain With then Method

Take a look at the code we've got on the previous step:

const promiseResponse = fetch('https://jsonplaceholder.typicode.com/posts/99999999')
const promiseData = promiseResponse
  .then((response) => {
      if (!response.ok) return Promise.reject('whoops!')
      response.json()
    })
const data = promiseData.then(console.log)
data.catch((errorValue) => console.log(errorValue, 'catched here!'))

Let's get rid of temporary variables. We can just add thens to the fetch and other thens because both them return the promise.

fetch('https://jsonplaceholder.typicode.com/posts/99999999')
  .then((response) => {
    if (!response.ok) return Promise.reject('whoops!')
    response.json()
  })
  .then(console.log)
  .catch((errorValue) => console.log(errorValue, 'catched here!'))

Note how our promises Β«chainsΒ» one to another, this thing called promise chain! πŸŽ‰

In the second then there is no onRejected handler, so then just returns rejected promise as it is. That's why we successfully handle promise in the next catch.

🧱 Promise creation

Previously, we used the built-in browser function fetch to get a promise, that will be fulfilled or rejected after some time. We can create our own using Promise Constructor!

Take a look at its signature ↓

new Promise((resolve?: Function, reject?: Function) => any)

promise constructor signature

We provide a function to the constructor, this function receives two arguments (the name is up to you), both β€” functions. We can call them with only one argument β€” promise value, it will be the value we will get in our handlers onFulfilled and onRejected. We can call them to make our promise settled. resolve β€” to make the promise fulfilled, reject β€” to make the promise rejected.

Try to write promise that will be fulfilled after 1 second

const myPromise = new Promise((resolve, reject) => setTimeout(() => resolve('wee, success! :)'), 1000))
myPromise.then(alert)

Try the same with reject and catch

We can also create already fulfilled promises β€” Promise.resolve(), and rejected β€” Promise.reject() with optional value.

To be continued

Topics not covered

  1. finally method example
  2. Promises with async/await
  3. Work with multiple promises (Promise.race, Promise.all)
  4. Exciting code tasks, more examples
  5. Your proposals, improvements, and comments are appreciated!πŸŽ‰ β†’ Telegram yurka7321@gmail.com