This content originally appeared on DEV Community and was authored by Vladislav Zimnikov
Table of contents
- Introduction
- Promise
- First Examples
-
Handling Promise
- then()
- catch()
- finally()
- Chaining
-
Advanced Usage
- Server Request
- Multiple Requests
- First to settle
- First to fulfill
- Outro
Introduction
When it comes to JavaScript it executes code one line after another so next line of code will not start it's execution up until previous one have finished
But what if required operation takes too much time? Should we freeze whole application's execution waiting for result? Obviously, not
Most common and easily understandable example is request to some server. You never know how much time exactly there is needed to receive response. It may take few dozens of milliseconds or few seconds
And while waiting for response we still want our application to be responsive, usable and even let customer know about making server request like through showing some sort of loader animation
In addition, once application receives response it should process it somehow so we also need a way to tell the program how it should treat data it is going to receive in future and we are not able to know when exactly
Basically, we want to be able to tell our program such thing:
In background, without blocking whole execution process, make this request, wait for the response and once received perform given set of commands
But how can we achieve such kind of functionality if currently we are aware that JavaScript executes all of the code in strict sequence?
Promise
In short, Promise allows performing operations asynchronously
The concept of Promise is pretty straightforward:
Give it piece of code you want to execute asynchronously and specify in what case Promise should fulfil successfully and vice versa
To understand principle of Promise better before diving into examples few theory concepts have to be discussed
First of all, when we create Promise with code to process asynchronously, we should pass this code in form of callback.
Callback - function passed as an argument for another function that is going to be executed at some specific point of latter function execution
Callback that is being passed to promise is called executor
Executor - callback function, invoked by Promise with two arguments being passed: resolve and reject
resolve - callback function that should be called when operation inside of Promise finishes successfully. Usually, accepts one parameter - result of operation performed inside of executor
reject - callback function that should be called in case when error happens during executor's code processing
In terms of making server request we would call resolve with response as an argument right after receiving it to proceed with it's further processing
First Examples
When we need to create promise we have to use constructor of Promise class with keyword new. Basically, we create instance of class Promise
As it was mentioned earlier, to create promise we should pass executor
Simplest example of how to create new Promise:
new Promise((resolve, reject) => {})
Currently, our promise does nothing. To actually see, how promise executes code in async manner let me show you next example
new Promise((resolve, reject) => {
setTimeout(() => {
console.log('in promise');
}, 1000)
})
console.log('after promise')
You can run this code right in browser or anywhere else to see what happens. First message to console we receive is 'after promise' and only after 1 second message 'in promise' is being printed out.
Function setTimeout
allows to delay execution of code and I am going to use this feature to demonstrate more capabilities of promises.
Handling Promise
Basically, promise can be fulfilled or rejected. If code inside of an executor processed well, callback resolve is being invoked and promise starts fulfilling. In opposite case, callback reject is called and rejection happens.
Promise gives us, developers, possibility to control what has to be done in case of promise fulfilment and rejection.
Object of Promise provides two methods for such thing: then
and catch
. Both accept callback
then()
To understand better, let's firstly set up our playground. First of all, I will create promise and save it to constant. Inside of an executor I will utilize setTimeout
to postpone invocation of resolve
function up to 1 second. To resolve
I will pass string 'Hello Promise'
const ourPromise = new Promise((resolve, reject) => {
setTimeout(() => resolve('Hello Promise'), 1000);
});
Now, let's use .then()
for the first time. I know that promise will pass string to callback on fulfilling, so let's print received message
ourPromise
.then((message) => console.log(message));
const ourPromise = new Promise((resolve, reject) => {
setTimeout(() => resolve('Hello Promise'), 1000);
});
ourPromise
.then((message) => console.log(message));
console.log('after promise');
If you run code above you can see that message 'after promise' is being printed first, just like last time. Only difference is that message 'Hello Promise' is being printed in callback passed to .then()
as an argument.
catch()
In case if executor's code fails, Promise should be rejected by reject invocation. To actually handle Promise's rejection, you should pass callback to .catch()
method of Promise instance. Usually, error being passed as an argument.
Let's consider next code example:
const ourPromise = new Promise((resolve, reject) => {
setTimeout(() => reject('Rejecting Promise'), 1000);
});
ourPromise
.catch((message) => console.log(message));
console.log('after promise');
It's almost the same as previous one, but we call reject instead of resolve and replace .then() with .catch(). As a result we get the same output in console.
finally()
It's pretty common occasion when you need to perform code on Promise's settlement despite the outcome. To avoid duplication of logic in .then
and .catch
methods there is .finally
.
Callback passed to method .finally
is going to be executed in any case: either Promise resolved successfully or was rejected
const ourPromise = new Promise(
(resolve, reject) => {
if (Math.random() > 0.5) {
reject('Rejecting');
}
resolve('Resolving');
}
);
ourPromise
.then(msg => console.log(msg))
.catch(err => console.log(err))
.finally(() => console.log('Final action'));
Important note
callback passed to
finally
does not accept any arguments
Chaining
Methods .then
, .catch
and .finally
return promise itself, so they can be chained. That's what makes working with Promises neat and concise. Try to run below example multiple times and see how in different cases different callbacks are being invoked
const ourPromise = new Promise(
(resolve, reject) => {
if (Math.random() > 0.5) {
reject('Rejecting');
}
resolve('Resolving');
}
);
ourPromise
.then(msg => console.log(msg))
.catch(err => console.log(err))
.finally(() => console.log('final action'));
Another important thing is that you are not limited to one .then
invocation, for example. There can be occasions when you receive another promise while handling previous one and to process new promise we can just return it and use .then
again. Let me just show you
const ourPromise = new Promise(
(resolve, reject) => {
if (Math.random() > 0.5) {
reject('Rejecting');
}
resolve('Resolving');
}
);
ourPromise
.then(msg => msg + ' x2')
.then(msg => console.log(msg))
.catch(err => console.log(err))
.finally(() => console.log('final action'));
As you can see from first callback I returned original message plus some extra characters. Value, returned from previous callback is being passed to next one.
Now let me show example with multiple Promises:
const ourPromise = new Promise(
(resolve, reject) => {
if (Math.random() > 0.5) {
reject('Rejecting');
}
resolve('Resolving');
}
);
ourPromise
.then(
msg => new Promise((resolve) => resolve('Another Promise'))
)
.then(msg => console.log(msg))
.catch(err => console.log(err))
.finally(() => console.log('final action'));
If you run code above and first promise is going to be resolved then you will see that message we passed to resolve function inside of our second Promise successfully got to callback we passed to our second .then
. This way we can handle multiple consecutive Promises in beautiful and convenient way
Advanced Usage
Server Request
Modern JS libraries and APIs for making and handling requests to server are built around Promise. Client side Fetch API is built around Promise
Let me show you one example using quite well-known library axios
const axios = require('axios');
axios.get('https://jsonplaceholder.typicode.com/users')
.then(res => console.log(res.data));
console.log('Something after request');
As you can see, we were able to perform request and process it response without blocking execution process
Multiple Requests
Sometimes there is need to perform multiple requests simultaneously. Specifically for this reason one of the methods provided by Promise can be utilised.
const axios = require('axios');
const requests = [
axios.get('https://jsonplaceholder.typicode.com/users'),
axios.get('https://jsonplaceholder.typicode.com/posts'),
axios.get('https://jsonplaceholder.typicode.com/comments')
]
Promise.all(requests)
.then(res => console.log(res))
Promise.all()
accepts iterable object like array, for instance, with promises we need to wait for. Once all Promises are resolved or at least one is rejected, then result of method all
which is Promise itself is resolved or rejected
First to settle
If you are waiting on multiple promises and you need to perform some action once one of them resolves or rejects there is method for that: Promise.race
There is an example:
const ourPromise = new Promise(
(resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
reject('Rejecting 1');
}
resolve('Resolving 1');
}, Math.floor(Math.random() * 1000))
}
);
const ourPromise2 = new Promise(
(resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
reject('Rejecting 2');
}
resolve('Resolving 2');
}, Math.floor(Math.random() * 1000))
}
);
Promise.race([ourPromise, ourPromise2])
.then(res => console.log(res))
.catch(res => console.log(res));
First to fulfill
If you want to perform action once first among many promises fulfils there is solution for you as well: Promise.any
. Be aware that Promise.any
exists in Node since version 15 so you might need to change the version of runner below
const ourPromise = new Promise(
(resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
reject('Rejecting 1');
}
resolve('Resolving 1');
}, Math.floor(Math.random() * 1000))
}
);
const ourPromise2 = new Promise(
(resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
reject('Rejecting 2');
}
resolve('Resolving 2');
}, Math.floor(Math.random() * 1000))
}
);
Promise.any([ourPromise, ourPromise2])
.then(res => console.log(res))
.catch(res => console.log(res));
Outro
That's it for today. Hope you enjoyed this little article.
Don't hesitate to leave a comment, I would appreciate it.
Have a nice day :)
This content originally appeared on DEV Community and was authored by Vladislav Zimnikov
Vladislav Zimnikov | Sciencx (2022-03-26T10:24:45+00:00) Modern JavaScript: Promise. Retrieved from https://www.scien.cx/2022/03/26/modern-javascript-promise/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.