# Promises
Promises are a better alternative to callbacks. They give you flexibility, intuitive syntax, and easy error handling.
Promises allow you to set an operation running (e.g. the fetching of an image from the server), and then wait until the result has returned before running another operation:
Main thread: Task A Task B
Promise: |__async operation__|
Since the operation is happening somewhere else, the main thread is not blocked while the async operation is being processed.
Think of Promises as a special function that either satisfy (resolve) or fail (reject) to execute a task, and then executes the corresponding actions - usually another task with the returned data in the case of 'resolved' and usually throw an error in the case of 'reject'.
They come with methods for the case that everything went well (resolve), and for the error case (reject).
var promise = new Promise((resolve, reject) => {
// do what your function is supposed to do
if (/* everything turned out fine */) {
resolve("It worked!");
}
else {
reject (Error("This didn’t work, because...")); }
});
You can then use your Promise like this:
promise.then((result) => {
console.log(result); // "It worked!"
}).catch((error) => {
console.log('Something went wrong: ' + error);
});
Use the function **then ** to execute code that should run in the case when everything went well, and the function catch to run code if there was an error.
Promises are specifically made for handling async operations, and have many advantages over old-style callbacks:
- You can chain multiple async operations together using multiple
.then()
operations, passing the result of one into the next one as an input. - Error handling is much better — all errors are handled by a single
.catch()
block at the end of the block
A good example is the fetch()
API, which is basically like a modern, more efficient version of XMLHttpRequest
(opens new window).
fetch('products.json')
.then((response) => {
return response.json();
}).then((json) => {
products = json;
initialize();
}).catch((err) => {
console.log('Fetch problem: ' + err.message);
});
- Here
fetch``()
is returning a promise (opens new window). - The promise is an object representing the completion or failure of the async operation.
- It's the browser's way of saying "I promise to get back to you with the answer as soon as I can," hence the name "promise."
Code blocks chained onto the end of the fetch()
:
- Two
then()
blocks. Both contain a callback function that will run if the previous operation is successful, and each callback receives as input the result of the previous successful operation - Each
.then()
block returns another promise, meaning that you can chain multiple.then()
blocks onto each other, so multiple asynchronous operations can be made to run in order, one after another. - The
catch()
block at the end runs if any of the.then()
blocks fail. an error object is made available inside thecatch()
, which can be used to report the kind of error that has occurred. - synchronous
try...catch
won't work with promises, but it will work with async/await (opens new window)
# The event queue
Async operations like promises are put into an event queue, which runs after the main thread has finished processing so that they do not block subsequent JavaScript code from running.
What the heck is the event loop anyway? (opens new window)
# Create Promises
Promises are objects that represent the eventual outcome of an asynchronous operation. A Promise
object can be in one of three states:
- Pending: The initial state— the operation has not completed yet. But we have a guarantee that it will settle.
- Fulfilled: The operation has completed successfully and the promise now has a resolved value. For example, a request’s promise might resolve with a JSON object as its value.
- Rejected: The operation has failed and the promise has a reason for the failure. This reason is usually an
Error
of some kind.
We refer to a promise as settled if it is no longer pending— it is either fulfilled or rejected**.**
To create a new Promise
object, we use the new
keyword and the Promise
constructor method:
const executorFunction = (resolve, reject) => {
if (someCondition) {
resolve('I resolved!');
} else {
reject('I rejected!');
}
}
const myFirstPromise = new Promise(executorFunction);
The
Promise
constructor method takes one argument: a callback called the executor function which runs automatically when the constructor is called.The executor function has two function parameters, usually referred to as the
resolve()
andreject()
functions.The
resolve()
andreject()
functions aren’t defined by the programmer. When thePromise
constructor runs, JavaScript will pass its ownresolve()
andreject()
functions into the executor function.resolve
is a function with one argument. Under the hood, if invoked,resolve()
will change the promise’s status frompending
tofulfilled
, and the promise’s resolved value will be set to the argument passed intoresolve()
.reject
is a function that takes a reason or error as an argument. Under the hood, if invoked,reject()
will change the promise’s status frompending
torejected
, and the promise’s rejection reason will be set to the argument passed intoreject()
.
const inventory = {
sunglasses: 1900,
pants: 1088,
bags: 1344
};
const myExecutor = (resolve, reject) => {
if (inventory.sunglasses > 0) {
resolve('Sunglasses order processed.');
} else {
reject('That item is sold out.')
}
}
const orderSunglasses = () => {
return new Promise(myExecutor)
};
let orderPromise = orderSunglasses();
console.log(orderPromise);
//_ ES6: 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
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();
// 2nd promise
var showOff = (phone) => {
return new Promise(
function (resolve, reject) {
var message = 'Hey friend, I have a new ' +
phone.color + ' ' + phone.brand + ' phone';
resolve(message);
}
);
};
this can be shortened:
// shorten it
// 2nd promise
var showOff = phone => {
var message = 'Hey friend, I have a new ' +
phone.color + ' ' + phone.brand + ' phone';
return Promise.resolve(message);
};
# Consuming Promises .then
The .then()
method of a JavaScript Promise
object can be used to get the eventual result (or error) of the asynchronous operation.
.then()
accepts two function arguments. The first handler supplied to it will be called if the promise is resolved. The second one will be called if the promise is rejected.
It takes two callback functions as arguments. We refer to these callbacks as handlers.
- The first handler, sometimes called
onFulfilled
, is a success handler, and it should contain the logic for the promise resolving. - The second handler, sometimes called
onRejected
, is a failure handler, and it should contain the logic for the promise rejecting.
We can invoke .then()
with one, both, or neither handler!
promise.then(function(result) {
console.log(result); // "Stuff worked!"
}, function(err) {
console.log(err); // Error: "It broke"
});
If the appropriate handler is not provided, instead of throwing an error, .then()
will just return a promise with the same settled value as the promise it was called on.
One important feature of .then()
is that it always returns a promise!
When you return something from a then()
callback, it's a bit magic. If you return a value, the next then()
is called with that value. However, if you return something promise-like, the next then()
waits on it, and is only called when that promise settles (succeeds/fails)
# The onFulfilled and onRejected Functions
To handle a “successful” promise, we invoke .then()
on the promise, passing in a success handler callback function:
const prom = new Promise((resolve, reject) => {
resolve('Yay!');
});
const handleSuccess = (resolvedValue) => {
console.log(resolvedValue);
};
prom.then(handleSuccess); // Prints: 'Yay!'
prom
is a promise which will resolve to'Yay!'
.- We define a function,
handleSuccess()
, which prints the argument passed to it. - We invoke
prom
‘s.then()
function passing in ourhandleSuccess()
function. - Since
prom
resolves,handleSuccess()
is invoked withprom
's resolved value,'Yay'
, so'Yay'
is logged to the console.
With typical promise consumption, we won’t know whether a promise will resolve or reject, so we’ll need to provide the logic for either case. We can pass both an onFulfilled
and onRejected
callback to .then()
.
let prom = new Promise((resolve, reject) => {
let num = Math.random();
if (num < .5 ){
resolve('Yay!');
} else {
reject('Ohhh noooo!');
}
});
const handleSuccess = (resolvedValue) => {
console.log(resolvedValue);
};
const handleFailure = (rejectionReason) => {
console.log(rejectionReason);
};
prom.then(handleSuccess, handleFailure);
# Chaining .then
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('foo');
}, 300);
});
myPromise
.then(handleResolvedA, handleRejectedA)
.then(handleResolvedB, handleRejectedB)
.then(handleResolvedC, handleRejectedC);
myPromise
.then(handleResolvedA)
.then(handleResolvedB)
.then(handleResolvedC)
.catch(handleRejectedAny);
# Using catch() with Promises
To create even more readable code, we can use a different promise function: .catch()
.
The .catch()
function takes only one argument, onRejected
In the case of a rejected promise, this failure handler will be invoked with the reason for rejection.
prom
.then((resolvedValue) => {
console.log(resolvedValue);
})
.catch((rejectionReason) => {
console.log(rejectionReason);
});
There's nothing special about catch()
, it's just sugar for then(undefined, func)
,
# Chaining Multiple Promises
One common pattern we’ll see with asynchronous programming is multiple operations which depend on each other to execute or that must be executed in a certain order.
The process of chaining promises together is called composition.
firstPromiseFunction()
.then((firstResolveVal) => {
return secondPromiseFunction(firstResolveVal);
})
.then((secondResolveVal) => {
console.log(secondResolveVal);
});
In order for our chain to work properly, we had to return
the promise secondPromiseFunction(firstResolveVal)
.
This ensured that the return value of the first .then()
was our second promise
# Avoiding Common Mistakes
Mistake 1: Nesting promises instead of chaining them.
returnsFirstPromise()
.then((firstResolveVal) => {
return returnsSecondValue(firstResolveVal)
.then((secondResolveVal) => {
console.log(secondResolveVal);
})
})
Mistake 2: Forgetting to return
a promise.
returnsFirstPromise()
.then((firstResolveVal) => {
returnsSecondValue(firstResolveVal)
})
.then((someVal) => {
console.log(someVal);
})
Let’s break down what’s happening in the example:
- We invoke
returnsFirstPromise()
which returns a promise. - We invoke
.then()
with a success handler. - Inside the success handler, we create our second promise, but we forget to
return
it! - We invoke a second
.then()
. It’s supposed to handle the logic for the second promise, but since we didn’t return, this.then()
is invoked on a promise with the same settled value as the original promise!
Since forgetting to return our promise won’t throw an error, this can be a really tricky thing to debug!
# Promise.all()
To maximize efficiency we should use concurrency, multiple asynchronous operations happening together. With promises, we can do this with the function Promise.all()
.
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
}, 300);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
}, 200);
});
Promise.all([promise1, promise2]).then((res) => {
console.log(res[0]);
console.log(res[1]);
});
The JavaScript Promise.all()
method can be used to execute multiple promises in parallel. The function accepts an array of promises as an argument. If all of the promises in the argument are resolved, the promise returned from Promise.all()
will resolve to an array containing the resolved values of all the promises in the order of the initial array.
For example, if you want to download multiple things at once.
Promise.all()
accepts an array of promises as its argument and returns a single promise.
- If every promise in the argument array resolves, the single promise returned from
Promise.all()
will resolve with an array containing the resolve value from each promise in the argument array. - If any promise from the argument array rejects, the single promise returned from
Promise.all()
will immediately reject with the reason that promise rejected. This behavior is sometimes referred to as failing fast.
Promise.all([img1.ready(), img2.ready()])
.then(function() {
// all loaded
}, function() {
// one or more failed
});
# Review
Promises are a difficult concept even for experienced developers
- Promises are JavaScript objects that represent the eventual result of an asynchronous operation.
- Promises can be in one of three states: pending, resolved, or rejected.
- A promise is settled if it is either resolved or rejected.
- We construct a promise by using the
new
keyword and passing an executor function to thePromise
constructor method. - We use
.then()
with a success handler callback containing the logic for what should happen if a promise resolves. - We use
.catch()
with a failure handler callback containing the logic for what should happen if a promise rejects. - Promise composition enables us to write complex, asynchronous code that’s still readable. We do this by chaining multiple
.then()
‘s and.catch()
‘s. - To use promise composition correctly, we have to remember to
return
promises constructed within a.then()
- We should chain multiple promises rather than nesting them.
- To take advantage of concurrency, we can use
Promise.all()
.