A promise is an object that may produce a single value sometime in the future: either a resolved value or a reason that it’s not resolved (e.g., a network error occurred).
A Promise
is in one of these states:
Promise users can attach callbacks to handle the fulfilled value or the reason for rejection.
Fire up a code editor, and create an index.html
file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Promises, promises</title>
</head>
<body>
<script>
// Promises code will go here
</script>
</body>
</html>
http-server
is a simple, zero-configuration command-line http server. It is powerful enough for production usage, but it's simple and hackable enough to be used for testing, local development, and learning.
$ npm install -g http-server
To run it type http-server
from the folder where you created the index.html
file.
A real-life Promise in short:
"Imagine you are a kid. Your parents promise you that they'll get you a new phone next week."
You don't know if you will get the phone until next week. Your parents can either really buy you a brand new phone, or stand you up and withhold the phone if they are not happy :(, this is a promise.
As stated in the introduction a promise has 3 states. They are:
new Promise( /* executor */ function(resolve, reject) { ... } );
executor: a function that is passed with the arguments resolve
and reject
. The executor
function is executed immediately by the Promise implementation before the Promise
constructor even returns the created object. The resolve
and reject
are functions, that when called, resolve or reject the promise, respectively. The executor usually initiates some asynchronous work, and then, once that completes, either calls the resolve
function passing the result or else calls reject
if an error occurred.
Let's convert the phone buying promise to JavaScript
const areParentsHappy = false;
// Promise
const willGetNewPhone = new Promise((resolve, reject) => {
if (areParentsHappy) {
const phone = {
brand: 'Apple',
model: 'iPhone Xs',
color: 'Space Gray'
};
resolve(phone); // fulfilled
} else {
const reason = new Error('parents are not happy');
reject(reason); // reject
}
});
A Promise object serves as a link between the executor (the "producing code") and the consuming functions, which receives the result or error. Consuming functions can be registered (subscribed) using the methods .then
and .catch
.
Promise.prototype.catch(onRejected)
Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled.
Promise.prototype.then(onFulfilled, onRejected)
Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler, or to its original settled value if the promise was not handled (i.e., if the relevant handler onFulfilled or onRejected is not a function).
Promise.prototype.finally(onFinally)
Appends a handler to the promise, and returns a new promise which is resolved when the original promise is resolved. The handler is called when the promise is settled, whether fulfilled or rejected.
const askParents = () => {
willGetNewPhone
.then(fulfilled => {
// yay, you got a new phone
console.log(fulfilled);
// output: { brand: 'Apple', model: 'iPhone Xs', color: 'Space Gray' }
})
.catch(error => {
// oops, parents didn't buy it
console.log(error.message);
// output: parents are not happy
});
};
askParents();
Promises are chainable.
Let's say, you, the kid, promise your friend that you will show him the new phone when your parents buy you one.
That's another promise. Let's write it!
const showOff = (phone) => Promise.resolve(`Hey bro, I have a new ${phone.color} ${phone.brand} ${phone.model} phone`);
Let's chain the promises. You, the kid can only start to showOff
after you willIGetNewPhone
. Adapt the askParents
function.
const askParents = () => {
willGetNewPhone
.then(showOff) // chain it here
.then(fulfilled => {
console.log(fulfilled);
// output: 'Hey bro, I have a new Space Gray Apple iPhone Xs phone'
})
.catch(error => {
// oops, parents didn't buy it
console.log(error.message);
// output: parents are not happy
});
};
That's how easy to chain the promise.
Promises are asynchronous. Let's log a message before and after we call the promise.
const askParents = () => {
console.log('Before asking parents'); //log before
willGetNewPhone
.then(showOff)
.then(fulfilled => {
console.log(fulfilled);
})
.catch(error => {
console.log(error.message);
});
console.log('After asking parents'); //log after
};
What is the sequence of expected output? Probably you expect:
However, the actual output sequence is:
You, the kid, wouldn't stop playing while waiting for your parents' promise (the new phone). Don't you? That's something we call asynchronous, and the code will run without blocking or waiting for the result. Anything that needs to wait for the promise to proceed, you put that in .then
.
Promise has 2 more useful methods
As per MDN documentation
The Promise.all(iterable)
method returns a single Promise
that resolves when all of the promises in the iterable argument have resolved or when the iterable argument contains no promises. It rejects with the reason of the first promise that rejects.
Promise.all([promise1, promise2]).then(results => {
// Both promises resolved
})
.catch(error => {
// One or more promises was rejected
});
Promise.race
is an interesting function -- instead of waiting for all promises to be resolved or rejected, Promise.race
triggers as soon as any promise in the array is resolved or rejected.
Promise.race([promise1, promise2]).then(result => {
// promise1 or promise2 resolved
})
.catch(error => {
// promise1 or promise2 rejected
});
Promise.all
Promise.race
Async/Await allows us to write asynchronous JavaScript that looks synchronous. Async/Await it is built on top of Promises (non-blocking, etc.), yet allows for code to be readable and reads as if it were synchronous.
The async
and await
syntax where introduced in ECMAScript 2016 (ES7). As stated above it make the asynchronous syntax look prettier and easier to understand, without the .then
and .catch
Let's rewrite our example with async/await
syntax:
const areParentsHappy = false;
// Promise
const willGetNewPhone = new Promise((resolve, reject) => {
if (areParentsHappy) {
const phone = {
brand: 'Apple',
model: 'iPhone Xs',
color: 'Space Gray'
};
resolve(phone); // fulfilled
} else {
const reason = new Error('parents are not happy');
reject(reason); // reject
}
});
// Friend promise
const showOff = async phone => Promise.resolve(`Hey bro, I have a new ${phone.color} ${phone.brand} ${phone.model} phone`);
// Call our promise
const askParents = async () => {
try {
console.log('Before asking parents');
const phone = await willGetNewPhone;
const message = await showOff(phone);
console.log(message);
}
catch (error) {
console.log(error.message);
}
finally {
console.log('After asking parents');
}
};
(async () => {
await askParents();
})();
Promises have become an integral part of several idioms in JavaScript, including the Async Functions standard used to make asynchronous code look synchronous.
Here is a checklist which breaks down the things we learned in this code lab.
.then
and .catch
Promise.all
Promise.race
async/await
Source code for this code lab can be found at https://github.com/The-Guide/fe-guild-pwa-promises