What is a Promise in JavaScript?
The code below uses arrow functions. I think they make the code more readable.
Why do we need Promises?
First of all, why do we need Promises?
Sometimes you want to write JavaScript code that will not return a result immediately. Maybe you want to request something to a server and it takes some seconds, or maybe you want to show a notification after 10 seconds.
For that, you can use Promises.
What is a Promise?
A good way to understand a promise is with an example.
Imagine that you want to eat pizza, so you order one. After some time, a tiny motorbike brings you a pizza.
And you eat it, nice! If everything was immediate, how would the code look like?
// Helper functions
const eat = (food) => console.log("I'm eating " + food);
const orderPizza = () => 'pepperoni pizza';
const pizza = orderPizza();
eat(pizza); // Shows "I'm eating pepperoni pizza"
In the code above, you order a pizza, get the pizza immediately and eat it. The only problem is that the code above is synchronous. You can think about synchronous code as code that blocks everything else while it's running.
So if you were a synchronous person, if you ordered a pizza, you'd get a "loading" message on your forehead and for 45 minutes you couldn't do anything else until your pizza arrived.
Now let's do it with a Promise. I'll explain the code after.
// Helper functions
const eat = (food) => console.log("I'm eating " + food);
const orderPizza = (successFn) => successFn('pepperoni pizza!'); //1
const p = new Promise(orderPizza); // 2
p.then(eat); // 3
It's a lot to take in, you'll get used to it with more examples.
The code is similar to the previous code. But there are some differences:
- Instead of returning a value, we pass it to a success function
successFn
. - We create a new Promise and pass it the orderPizza function.
- We call the method then on the object p.
And if you run this, it will also show "I'm eating a pepperoni pizza".
This code is asynchronous but the result is shown immediately. Still not a reason to use Promises though.
Let's make it more interesting by making you wait 2 seconds for your pizza:
// Helper function
const eat = (food) => console.log("I'm eating " + food); // 5
const orderPizza = (successFn) => {
// 2
setTimeout(() => {
successFn('pepperoni pizza!'); // 3
}, 2000);
};
const p = new Promise(orderPizza); // 1
p.then(eat); // 4
What is happening on the code above?
- You create a Promise.
- When the Promise is created, it immediately calls the setTimeout.
- After 2 seconds, the successFn is called.
- The
then
method receives the 'pepperoni pizza!' string and passes it to the function eat. - The eat function shows "I'm eating pepperoni pizza!" in the console.
But why all this if we could have just called the setTimeout?! We're getting there.
What do you think will happen if we run the code below?
setTimeout(() => console.log(1), 1000);
console.log(2);
// result:
// 2
// 1
It will show on the console the value 2
and after 1 second, it shows the value 1
.
That doesn't seem right! We want to show the second log after the first one.
Watch the video JavaScript event loop to understand why this happens.
Let's do it with a promise:
// Helper function, executor is a fancy name of the function
// passed to the Promise.
const executor = (successFn) => {
setTimeout(() => successFn('on timeout inside promise'), 1000);
};
const after = () => console.log('After promise');
const p = new Promise(executor);
p.then(after);
Now it works as expected! It's confusing but it works, woohoo! It shows the 'After promise' after the 'on timeout inside promise'.
But you're not celebrating are you?
Why write all that code instead of moving the console.log inside the setTimeout? Because stuffing all the code inside the callback gets very complex very fast.
Callbacks can become much more unreadable than Promises, even if it's easier to learn callbacks.
JavaScript Promise Then
We used the then
method already, but how does it work?
The then
method is called on a Promise. It's called when the Promise has finished working.
The Promise has finished working when:
- The function passed to the Promise calls the
resolve
function or, - The function passed to the Promise calls the
reject
function or, - The function passed to the Promise throws an error.
Let's go back to our pizza example:
- We ordered the pizza
- We wait 2 seconds
- We get the pizza OR we receive a message warning the pizzeria is closed.
// The executor function, the function passed to the Promise.
const executor = (resolve, reject) => {
const closed = true;
if (closed) {
reject('Pizzeria is closed!');
} else {
setTimeout(function () {
resolve('pepperoni pizza!');
}, 2000);
}
};
const onSuccess = (msg) => console.log("I'm eating " + msg);
const onFail = (msg) => console.log('Oh no! ' + msg);
const p = new Promise(executor);
p.then(onSuccess, onFail);
If the closed
variable is true
, you get the message "Oh no! Pizzeria is closed!".
Notice you don't need to wait 2 seconds, it's immediate.
If you change closed
to false
, after 2 seconds you'll get "I'm eating pepperoni pizza!".
JavaScript Promise resolve, reject and executor functions
There are 3 functions used all the time with Promises: The resolve, reject and executor functions. You can name it whatever you want though. Example:
// The executor function with the functions resolve and
// reject as parameters.
const executor = (resolve, reject) => {
const hasProblem = true;
if (hasProblem) {
reject(new Error('boom'));
} else {
resolve('all is fine');
}
};
// passed as the resolve function.
const onResolve = (result) => console.log(result);
// passed as the reject function.
const onReject = (error) => console.log(error.message);
const p = new Promise(executor);
p.then(onResolve, onReject);
executor
: The function you pass to the Promise.resolve
: The function passed to the executor and called when everything goes well.reject
: The function passed to the executor and called when something goes wrong.
Let's see a diagram:
Let's follow the paths on the diagram if everything goes well:
- The
executor
function is called when you create the Promise. - The
executor
function calls theresolve
orreject
function. - If the
resolve
is called, theonResolve
passed to thethen
function is called.
Questions
What's the output?
setTimeout(function () {
console.log(1)
}, 0)
console.log(2)
What's the output?
const exec = (res) => setTimeout(() => res(), 0);
const f1 = () => console.log(1);
const f2 = () => console.log(2);
const p = new Promise(exec);
p.then(f1);
f2();
What's the output?
const exec = (res) => setTimeout(() => res(), 0);
const f1 = () => console.log(1);
const f2 = () => console.log(2);
const p = new Promise(exec);
p.then(f1).then(f2);
What's the output?
const exec = (res, rej) => {
if(typeof res === "function") res();
rej();
});
const f1 = () => console.log("ok");
const f2 = () => console.log("nok");
new Promise(exec).then(f1, f2)