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
π€ 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.
Promise's possible states
- before the result is ready β the promise's state is
pending
(we already saw this) - when the result is available β the promise's state is
fulfilled
- when an error happened β the promise's state is
rejected
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 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 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
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 then
s to the fetch
and other then
s 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.
Topics not covered
finally
method example- Promises with
async/await
- Work with multiple promises (
Promise.race
,Promise.all
) - Exciting code tasks, more examples
- Your proposals, improvements, and comments are appreciated!π β Telegram yurka7321@gmail.com