A Promise in short:
“Imagine you are a kid. Your mom promises you that she’ll get you a new phone next week.”
You don’t know if you will get that phone until next week. Your mom can either really buy you a brand new phone, or stand you up and withhold the phone if she is not happy :(.
That is a promise. A promise has 3 states. They are:
- Promise is pending: You don’t know if you will get that phone until next week.
- Promise is resolved: Your mom really buy you a brand new phone.
- Promise is rejected: You don’t get a new phone because your mom is not happy.
Let’s convert this to JavaScript.
var isMomHappy = false;
var willIGetNewPhone = new Promise(
function (resolve, reject) {
if (isMomHappy) {
var phone = {
brand: 'Samsung',
color: 'black'
};
resolve(phone);
} else {
var reason = new Error('mom is not happy');
reject(reason);
}
}
);
The code is quite expressive in itself.
- We have a boolean
isMomHappy
, to define if mom is happy. - We have a promise
willIGetNewPhone
. The promise can be either resolved
(if mom get you a new phone) or rejected
(mom is not happy, she don’t buy you one). - There is a standard syntax to define a new
Promise
, refer to MDN documentation, a promise syntax look like this.
new Promise( function (resolve, reject) { ... } );
- What you need to remember is, when the result is successful, call
resolve(your_success_value)
, if the result fails, call reject(your_fail_value)
in your promise. In our example, if mom is happy, we will get a phone. Therefore, we call resolve
function with phone
variable. If mom is not happy, we will call reject
function with a reason reject(reason)
;
Now that we have the promise, let’s consume it.
...
var askMom = function () {
willIGetNewPhone
.then(function (fulfilled) {
console.log(fulfilled);
})
.catch(function (error) {
console.log(error.message);
});
};
askMom();
- We have a function call
askMom
. In this function, we will consume our promise willIGetNewPhone
. - We want to take some action once the promise is resolved or rejected, we use
.then
and .catch
to handle our action. - In our example, we have
function(fulfilled) { ... }
in .then
. What is the value of fulfilled
? The fulfilled
value is exactly the value you pass in your promise resolve(your_success_value)
. Therefore, it will be phone
in our case. - We have
function(error){ ... }
in .catch
. What is the value of error
? As you can guess, the error
value is exactly the value you pass in your promise resolve(your_fail_value)
. Therefore, it will be reason
in our case.
Let’s run the example and see the result!
Demo: https://jsbin.com/nifocu/1/edit?js,console
Promises are chainable.
Let’s say, you, the kid, promise your friend that you will show them the new phone when your mom buy you one.
That is another promise. Let’s write it!
...
var showOff = function (phone) {
return new Promise(
function (resolve, reject) {
var message = 'Hey friend, I have a new ' +
phone.color + ' ' + phone.brand + ' phone';
resolve(message);
}
);
};
Notes:
- In this example, you might realize we didn’t call the
reject
. It’s optional. - We can shorten this sample like using
Promise.resolve
instead.
...
var showOff = function (phone) {
var message = 'Hey friend, I have a new ' +
phone.color + ' ' + phone.brand + ' phone';
return Promise.resolve(message);
};
Let’s chain the promises. You, the kid can only start the showOff
promise after the willIGetNewPhone
promise.
...
var askMom = function () {
willIGetNewPhone
.then(showOff)
.then(function (fulfilled) {
console.log(fulfilled);
})
.catch(function (error) {
console.log(error.message);
});
};
That’s how easy to chain the promise.
Promises are asynchronous. Let’s log a message before and after we call the promise.
var askMom = function () {
console.log('before asking Mom');
willIGetNewPhone
.then(showOff)
.then(function (fulfilled) {
console.log(fulfilled);
})
.catch(function (error) {
console.log(error.message);
});
console.log('after asking mom');
}
What is the sequence of expected output? Probably you expect:
1. before asking Mom
2. Hey friend, I have a new black Samsung phone.
3. after asking mom
However, the actual output sequence is:
1. before asking Mom
2. after asking mom
3. Hey friend, I have a new black Samsung phone.
Why? Because life (or JS) waits for no man.
You, the kid, wouldn’t stop playing while waiting for your mom promise (the new phone). Don’t you? That’s something we call asynchronous, the code will run without blocking or waiting for the result. Anything that need to wait for promise to proceed, you put that in .then
.
ES5 – Majority browsers
The demo code is workable in ES5 environments (all major browsers + NodeJs) if you include Bluebird promise library. It’s because ES5 doesn’t support promises out of the box. Another famous promise library is Q by Kris Kowal.
ES6 / ES2015 – Modern browsers, NodeJs v6
The demo code works out of the box because ES6 supports promises natively. In addition, with ES6 functions, we can further simplify the code with fat arrow => and use const
and let
.
Here is an example of ES6 code:
const isMomHappy = true;
const willIGetNewPhone = new Promise(
(resolve, reject) => {
if (isMomHappy) {
const phone = {
brand: 'Samsung',
color: 'black'
};
resolve(phone);
} else {
const reason = new Error('mom is not happy');
reject(reason);
}
}
);
const showOff = function (phone) {
const message = 'Hey friend, I have a new ' +
phone.color + ' ' + phone.brand + ' phone';
return Promise.resolve(message);
};
const askMom = function () {
willIGetNewPhone
.then(showOff)
.then(fulfilled => console.log(fulfilled))
.catch(error => console.log(error.message));
};
askMom();
Notes that all the var
are replaced with const
. All the function(resolve, reject)
has been simplified to (resolve, reject) =>
. There are a few benefits come with these changes. Read more on:-
ES7 – Async Await make the syntax look prettier
ES7 introduce async
and await
syntax. It makes the asynchronous syntax look prettier and easier to understand, without the .then
and .error
.
Rewrite our example with ES7 syntax.
const isMomHappy = true;
const willIGetNewPhone = new Promise(
(resolve, reject) => {
if (isMomHappy) {
const phone = {
brand: 'Samsung',
color: 'black'
};
resolve(phone);
} else {
const reason = new Error('mom is not happy');
reject(reason);
}
}
);
async function showOff(phone) {
return new Promise(
(resolve, reject) => {
var message = 'Hey friend, I have a new ' +
phone.color + ' ' + phone.brand + ' phone';
resolve(message);
}
);
};
async function askMom() {
try {
console.log('before asking Mom');
let phone = await willIGetNewPhone;
let message = await showOff(phone);
console.log(message);
console.log('after asking mom');
}
catch (error) {
console.log(error.message);
}
}
(async () => {
await askMom();
})();
- Whenever you need to return a promise in a function, you prepend
async
to that function. E.g. async function showOff(phone)
- Whenever you need to call a promise, you prepend with
await
. E.g. let phone = await willIGetNewPhone;
and let message = await showOff(phone);
. - Use
try { ... } catch(error) { ... }
to catch promise error, the rejected promise.
Why do we need promises? How’s the world look like before promise? Before answering these questions, let’s go back to the fundamental.
Normal Function vs Async Function
Let’s take a look at these two example, both example perform addition of two number, one add using normal function, the other add remotely.
Normal Function to Add Two Numbers
function add (num1, num2) {
return num1 + num2;
}
const result = add(1, 2);
Async Function to Add Two numbers
const result = getAddResultFromServer('http://www.example.com?num1=1&num2=2');
If you add the numbers with normal function, you get the result immediately. However when you issue a remote call to get result, you need to wait, you can’t get the result immediately.
Or put it this way, you don’t know if you will get the result because the server might be down, slow in response, etc. You don’t want your entire process to be blocked while waiting for the result.
Calling APIs, downloading files, reading files are among some of the usual async operations that you’ll perform.
World Before Promises: Callback
Must we use promise for asynchronous call? Nope. Prior to Promise, we use callback. Callback is just a function you call when you get the return result. Let’s modify the previous example to accept a callback.
function addAsync (num1, num2, callback) {
return $.getJSON('http://www.example.com', {
num1: num1,
num2: num2
}, callback);
}
addAsync(1, 2, success => {
const result = success;
});
The syntax looks ok, why do we need promises then?
What if You Want to Perform Subsequent Async Action?
Let’s say, instead of just add the numbers one time, we want to add 3 times. In a normal function, we do this:-
let resultA, resultB, resultC;
function add (num1, num2) {
return num1 + num2;
}
resultA = add(1, 2);
resultB = add(resultA, 3);
resultC = add(resultB, 4);
console.log('total' + resultC);
console.log(resultA, resultB, resultC);
How it looks like with callbacks?
let resultA, resultB, resultC;
function addAsync (num1, num2, callback) {
return $.getJSON('http://www.example.com', {
num1: num1,
num2: num2
}, callback);
}
addAsync(1, 2, success => {
resultA = success;
addAsync(resultA, 3, success => {
resultB = success;
addAsync(resultB, 4, success => {
resultC = success;
console.log('total' + resultC);
console.log(resultA, resultB, resultC);
});
});
});
Demo: https://jsbin.com/barimo/edit?html,js,console
The syntax is less user friendly. In a nicer term, It looks like a pyramid, but people usually refer this as “callback hell”, because the callback nested into another callback. Imagine you have 10 callbacks, your code will nested 10 times!
Escape From Callback Hell
Promises come in to rescue. Let’s look at the promise version of the same example.
let resultA, resultB, resultC;
function addAsync(num1, num2) {
return fetch(`http://www.example.com?num1=${num1}&num2=${num2}`)
.then(x => x.json());
}
addAsync(1, 2)
.then(success => {
resultA = success;
return resultA;
})
.then(success => addAsync(success, 3))
.then(success => {
resultB = success;
return resultB;
})
.then(success => addAsync(success, 4))
.then(success => {
resultC = success;
return resultC;
})
.then(success => {
console.log('total: ' + success)
console.log(resultA, resultB, resultC)
});
Demo: https://jsbin.com/qafane/edit?js,console
With promises, we flatten the callback with .then
. In a way, it looks cleaner because of no callback nesting. Of course, with ES7 async
syntax, we can even further enhance this example, but I leave that to you. 🙂
Before you settle down with promises, there is something that has come about to make it even easier to deal with async data called Observables
.
Observables are lazy event streams which can emit zero or more events, and may or may not finish.
Some key differences between promises and observable are:
- Observables are cancellable
- Observable are lazy
Fear not, let look at the same demo written with Observables. In this example, I am using RxJS for the observables.
let Observable = Rx.Observable;
let resultA, resultB, resultC;
function addAsync(num1, num2) {
const promise = fetch(`http://www.example.com?num1=${num1}&num2=${num2}`)
.then(x => x.json());
return Observable.fromPromise(promise);
}
addAsync(1,2)
.do(x => resultA = x)
.flatMap(x => addAsync(x, 3))
.do(x => resultB = x)
.flatMap(x => addAsync(x, 4))
.do(x => resultC = x)
.subscribe(x => {
console.log('total: ' + x)
console.log(resultA, resultB, resultC)
});
Demo: https://jsbin.com/dosaviwalu/edit?js,console
Notes:
Observable.fromPromise
converts a promise to observable stream..do
and .flatMap
are among some of the operators available for Bbservables- Streams are lazy. Our
addAsync
runs when we .subscribe
to it.
Observables can do more funky stuff easily. For example, delay
add function by 3 seconds
with just one line of code or retry so you can retry a call a certain number of times.
...
addAsync(1,2)
.delay(3000)
.do(x => resultA = x)
...
Well, let’s talk about Observables in future post!
Get yourself familiar with callbacks and promises. Understand them and use them. Don’t worry about Observables, just yet. All three can factor into your development depending on the situation.
Here are the demo code for all mom promise to buy phone
examples:
That’s it. Hopefully this article smoothen your path to tame the JavaScript promises. Happy coding!