Javascript Promises can be challenging to understand. Therefore, I would like to write down the way I understand promises.
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 really buy you a brand new phone, or she doesn’t.
That is a promise. A promise has three states. They are:
Let’s convert this to JavaScript.
// ES5: Part 1
var isMomHappy = false;
// Promise
var willIGetNewPhone = new Promise(
function (resolve, reject) {
if (isMomHappy) {
var phone = {
brand: 'Samsung',
color: 'black'
};
resolve(phone); // fulfilled
} else {
var reason = new Error('mom is not happy');
reject(reason); // reject
}
}
);
The code is quite expressive in itself.
Below is how a promise syntax looks normally:
// promise syntax look like this
new Promise(function (resolve, reject) { ... } );
Now that we have the promise, let’s consume it:
// ES5: Part 2
var willIGetNewPhone = ... // continue from part 1
// call our promise
var askMom = function () {
willIGetNewPhone
.then(function (fulfilled) {
// yay, you got a new phone
console.log(fulfilled);
// output: { brand: 'Samsung', color: 'black' }
})
.catch(function (error) {
// oops, mom didn't buy it
console.log(error.message);
// output: 'mom is not happy'
});
};
askMom();
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 buys you one.
That is another promise. Let’s write it!
// ES5
// 2nd promise
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: We can shorten the above code by writing as below:
// shorten it
// 2nd promise
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.
// call our promise
var askMom = function () {
willIGetNewPhone
.then(showOff) // chain it here
.then(function (fulfilled) {
console.log(fulfilled);
// output: 'Hey friend, I have a new black Samsung phone.'
})
.catch(function (error) {
// oops, mom don't buy it
console.log(error.message);
// output: 'mom is not happy'
});
};
That is how you can chain the promise.
Promises are asynchronous. Let’s log a message before and after we call the promise.
// call our promise
var askMom = function () {
console.log('before asking Mom'); // log before
willIGetNewPhone
.then(showOff)
.then(function (fulfilled) {
console.log(fulfilled);
})
.catch(function (error) {
console.log(error.message);
});
console.log('after asking mom'); // log after
}
What is the sequence of expected output? You might 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.
You wouldn’t stop playing while waiting for your mom’s promise (the new phone). That’s something we call asynchronous: the code will run without blocking or waiting for the result. Anything that needs to wait for a promise to proceed is put in .then
.
Here is the full example in ES5:
// ES5: Full example
var isMomHappy = true;
// Promise
var willIGetNewPhone = new Promise(
function (resolve, reject) {
if (isMomHappy) {
var phone = {
brand: 'Samsung',
color: 'black'
};
resolve(phone); // fulfilled
} else {
var reason = new Error('mom is not happy');
reject(reason); // reject
}
}
);
// 2nd promise
var showOff = function (phone) {
var message = 'Hey friend, I have a new ' +
phone.color + ' ' + phone.brand + ' phone';
return Promise.resolve(message);
};
// call our promise
var askMom = function () {
willIGetNewPhone
.then(showOff) // chain it here
.then(function (fulfilled) {
console.log(fulfilled);
// output: 'Hey friend, I have a new black Samsung phone.'
})
.catch(function (error) {
// oops, mom don't buy it
console.log(error.message);
// output: 'mom is not happy'
});
};
askMom();
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.
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 an arrow function and use const
and let
.
Here is the full example in ES6 code:
//_ ES6: Full example_
const isMomHappy = true;
// Promise
const willIGetNewPhone = new Promise(
(resolve, reject) => { // fat arrow
if (isMomHappy) {
const phone = {
brand: 'Samsung',
color: 'black'
};
resolve(phone);
} else {
const reason = new Error('mom is not happy');
reject(reason);
}
}
);
// 2nd promise
const showOff = function (phone) {
const message = 'Hey friend, I have a new ' +
phone.color + ' ' + phone.brand + ' phone';
return Promise.resolve(message);
};
// call our promise
const askMom = function () {
willIGetNewPhone
.then(showOff)
.then(fulfilled => console.log(fulfilled)) // fat arrow
.catch(error => console.log(error.message)); // fat arrow
};
askMom();
Note that all the var
are replaced with const
. All of the function(resolve, reject)
have been simplified to (resolve, reject) =>
. There are a few benefits that come with these changes.
ES7 introduced async
and await
syntax. It makes the asynchronous syntax easier to understand, without the .then
and .catch
.
Rewrite our example with ES7 syntax:
// ES7: Full example
const isMomHappy = true;
// Promise
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);
}
}
);
// 2nd promise
async function showOff(phone) {
return new Promise(
(resolve, reject) => {
var message = 'Hey friend, I have a new ' +
phone.color + ' ' + phone.brand + ' phone';
resolve(message);
}
);
};
// call our promise in ES7 async await style
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 it here too
(async () => {
await askMom();
})();
Why do we need promises? How did the world look before promises? Before answering these questions, let’s go back to the fundamentals.
Let’s take a look at these two examples. Both examples perform the addition of two numbers: one adds using normal functions, and the other adds remotely.
// add two numbers normally
function add (num1, num2) {
return num1 + num2;
}
const result = add(1, 2); // you get result = 3 immediately
// add two numbers remotely
// get the result by calling an API
const result = getAddResultFromServer('http://www.example.com?num1=1&num2=2');
// you get result = "undefined"
If you add the numbers with the normal function, you get the result immediately. However, when you issue a remote call to get the result, you need to wait, and you can’t get the result immediately.
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, and reading files are among some of the usual async operations that you’ll perform.
You do not need to use promises for an asynchronous call. Prior to promises, we used callbacks. Callbacks are a function you call when you get the return result. Let’s modify the previous example to accept a callback.
// add two numbers remotely
// get the result by calling an API
function addAsync (num1, num2, callback) {
// use the famous jQuery getJSON callback API
return $.getJSON('http://www.example.com', {
num1: num1,
num2: num2
}, callback);
}
addAsync(1, 2, success => {
// callback
const result = success; // you get result = 3 here
});
Instead of adding the numbers one at a time, we want to add three times. In a normal function, we would do this:-
// add two numbers normally
let resultA, resultB, resultC;
function add (num1, num2) {
return num1 + num2;
}
resultA = add(1, 2); // you get resultA = 3 immediately
resultB = add(resultA, 3); // you get resultB = 6 immediately
resultC = add(resultB, 4); // you get resultC = 10 immediately
console.log('total' + resultC);
console.log(resultA, resultB, resultC);
This is how this looks with callbacks:
// add two numbers remotely
// get the result by calling an API
let resultA, resultB, resultC;
function addAsync (num1, num2, callback) {
// use the famous jQuery getJSON callback API
// https://api.jquery.com/jQuery.getJSON/
return $.getJSON('http://www.example.com', {
num1: num1,
num2: num2
}, callback);
}
addAsync(1, 2, success => {
// callback 1
resultA = success; // you get result = 3 here
addAsync(resultA, 3, success => {
// callback 2
resultB = success; // you get result = 6 here
addAsync(resultB, 4, success => {
// callback 3
resultC = success; // you get result = 10 here
console.log('total' + resultC);
console.log(resultA, resultB, resultC);
});
});
});
Demo: https://jsbin.com/barimo/edit?html,js,console
This syntax is less user-friendly due to the deeply nested callbacks.
Promises can help you avoid deeply nested callbacks. Let’s look at the promise version of the same example:
// add two numbers remotely using observable
let resultA, resultB, resultC;
function addAsync(num1, num2) {
// use ES6 fetch API, which return a promise
// What is .json()? https://developer.mozilla.org/en-US/docs/Web/API/Body/json
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)
});
With promises, we flatten the callback with .then
. In a way, it looks cleaner because there is no callback nesting. With ES7 async
syntax, you could further enhance this example.
Before you settle down with promises, there is something that has come about to help you deal with async data called Observables
.
Let’s look at the same demo written with Observables. In this example, we will use RxJS for the observables.
let Observable = Rx.Observable;
let resultA, resultB, resultC;
function addAsync(num1, num2) {
// use ES6 fetch API, which return a promise
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)
});
Observables can do more interesting things. 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) // delay 3 seconds
.do(x => resultA = x)
...
You may read one of my RxJs posts here.
Familiarizing yourself with callbacks and promises is important. Understand them and use them. Don’t worry about Observables just yet. All three can factor into your development depending on the situation.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Thank you for the explanation! It really helped me to get basic knowledge how to use promises and modern async/await keywords. Thank you a lot!
This comment has been deleted