Promises
Open the project folder
In our starter files, open the index.html page from this lessons folder:
10.Async-JavaScript > 03.Promises
Introduction
We know now that making an asynchronous request can take time. Maybe a few milliseconds, a few seconds, minutes, or never complete at all.
Often when we are talking about this, it refers to fetching something from outside our website or app. Possibly a server, data store, API, or an external website.
The Promise object
And a Promise is great for handling this outcome. A Promise is an object, and this object represents the result of an async task. The result could be a failure, we may not get what we need from the server or, it may take a little time.
Promises hold onto a task and promise to tell you in the future when the outcome is known. And we have already seen promises and some of these outcomes already.
Using with the Fetch API
Earlier we looked at a simple example of fetching an image from Wikimedia, using the Fetch API:
const imageSrc = fetch(
'https://upload.wikimedia.org/wikipedia/commons/thumb/7/7d/Kamenn%C3%A1_l%C3%BAka_03.JPG/2560px-Kamenn%C3%A1_l%C3%BAka_03.JPG'
);
console.log(imageSrc);
Fetch is an async operation, and we see a Promise in the console. When we tried to access the Promise too early, before it finished getting the image, it was pending. After adding a time delay with a timeout, the result was fulfilled.
Available states
And these are two of the three available states with promises:
- Pending: initial state, no result yet.
- Fulfilled: task completed successfully.
- Rejected: task has failed.
Pending is the initial state when we don’t yet know if the task will complete or not. Then fulfilled when the task was deemed a success. Such as the data is returned and is ready to use.
Then we also have rejected for when the task has failed, we don’t have the data we requested, and we need to do something about it.
📝 You may also hear the word settled when using promises, settled is when we know the fate of the task, meaning it is either fulfilled or rejected.
We don’t need to do anything about the pending state, since we can’t tell what direction it will go with the result. But we do need to handle a success or failure.
Example
Over in the starter file we see a Fetch API example:
fetch('https://dog.ceo/api/breed/image/random');
This is fetching data from an external API. This is an external URL which selects a random dog image.
Copy this URL (without quotes), and paste it into the browser:
{
"message": "https://images.dog.ceo/breeds/otterhound/n02091635_3941.jpg",
"status": "success"
}
JSON
This response is in a format called JSON, which stands for JavaScript Object Notation. JSON is a syntax for exchanging data over the web.
It looks like a JavaScript Object, but is language independent, meaning it can be created and parsed by other languages too. Although it is structured similar to a JavaScript object, data is transferred as plain text.
The result is a random jpg image from the API. We access it with the message property. Also, a status of success.
The then method
As we know, fetch is async. We can handle the outcome of fetching this image with a Promise.
First, we will handle the fulfilled state, and we do this by chaining the then method onto the fetch:
fetch('https://dog.ceo/api/breed/image/random')
.then()
📝 When chaining the
then()method make sure the semi-colon is removed from the end of the fetch line.
Fetch returns a Promise as the response, and therefore we have access to this promise method called then.
Next we pass in a callback function to run if this was a success:
fetch('https://dog.ceo/api/breed/image/random')
.then(function () {
})
This is an async callback and we only run this code once the task was a success.
Of course, we want to access the data returned from fetch, and this can be passed to the function:
.then(function (response) {
console.log(response);
})
The Response object
You should see the following in the console:
Response {type: "cors", url: "https://dog.ceo/api/breeds/image/random", redirected: false, status: 200, ok: true, …}
body: ReadableStream
bodyUsed: false
headers: Headers {}
ok: true
redirected: false
status: 200
statusText: ""
type: "cors"
url: "https://dog.ceo/api/breeds/image/random"
[[Prototype]]: Response
This successful promise returns a Response object. This object contains data such as the status code, 200 means a success, and we see a URL.
You may notice the URL is not the one we want. This is the URL used to ask for the random image, not the jpg URL we had earlier in the browser.
This goes back to the JSON format. The data we need is stored inside the body property:
body: ReadableStream
The json() method
And to read this JSON data, this response body object has a json() method available:
fetch('https://dog.ceo/api/breeds/image/random')
.then(function (response) {
console.log(response.json());
});
Resulting in the following in the console:
Promise {<pending>}
We are back to a pending Promise. The task has not yet completed. This is because the json() method also returns a Promise which we need to handle.
There are a couple of ways we can do this. First, remove the console.log wrapper:
fetch('https://dog.ceo/api/breeds/image/random')
.then(function (response) {
response.json()
});
And since the json method returns a Promise, we can again chain onto the end the then method:
fetch('https://dog.ceo/api/breeds/image/random')
.then(function (response) {
response.json()
// also receives in data from the response
.then(function(data) {
console.log(data);
})
});
This is the same URL in the message property we seen when we pasted the link in the browser.
message: "https://images.dog.ceo/breeds/pointer-german/n02100236_2119.jpg"
status: "success"
Everything here returns a Promise. The fetch call, and the json method.
Alternative syntax
There is also an alternative syntax to avoid nesting the then methods like above. We can keep them all at the top level to help readability.
To do this, we can refactor as follows:
fetch('https://dog.ceo/api/breeds/image/random')
.then(function (response) {
// return json data
return response.json();
})
// chain the then method to the end:
.then(function (data) {
console.log(data);
});
We can chain as many of these then methods as we need to, each one waiting on the previous result before running. Keeping full control over our async tasks and the order in which they run.
Displaying the image in the browser
The data.message property gives us the URL we need for our image. And we can use it to display in the browser:
<body>
<!-- 1. add empty image element -->
<img src="" alt="" />
<script>
fetch("https://dog.ceo/api/breeds/image/random")
.then(function (response) {
return response.json();
})
.then(function (data) {
// 2. set image src
document.querySelector("img").src = data.message;
console.log(data.message);
});
</script>
Refresh the browser to see random dog images!
The catch method
This is the fulfilled state handled, what about the rejected state? This happens if there is an error fetching the data or completing the task we set.
For this, we chain the catch method to the end:
fetch('https://dog.ceo/api/breeds/image/random')
.then(function (response) {
return response.json();
})
.then(function (data) {
document.querySelector('img').src = data.message;
console.log(data.message);
})
.catch()
📝 Remember, when chaining any method, the semi-colon must be removed from the previous line.
This will run when there is an error, and pass that error to a callback function:
.catch(function (error) {
console.log("There was an error: " + error);
});
If there are no errors, we won’t see anything. To test this, we can switch off the network in the browser developer tools.
In Chrome, there will be an option in the network tab:

Other browsers will have similar options. This dropdown has an option to set the network to be offline. Switching off the network means we cannot get the image we want, and the Promise should throw an error in the console:
⚠️ There was an error: TypeError: Failed to fetch
The finally method
The last method we will cover is finally. The finally method will run regardless of if the Promise was fulfilled or rejected.
And this can also be chained too:
// ...
.catch(function (error) {
console.log('There was an error: ' + error);
})
.finally(function () {
console.log('I will always run');
});
The code in here will always run. You can again test this with the network settings. The finally method is useful to follow up with some code to run after the promise has settled. The alternative would be to place this code into both the then and the catch sections, resulting in duplicated code.
This example was possible because the fetch method initially returns a promise to start the whole chain. But what about things that don’t return a promise? Next, we will look at how we can handle this.