JavaScript Promise API Basics
Promise object represent a task that will eventually be completed. It offers many wonderful features and api that make it easy to deal with tasks that will take an indefinite amount of time to complete. Things like network calls, querying the database, or doing anything that might be intensive like rendering lots of items, should use promise.
Why Promises?
When dealing with a situation where it is difficult to determine when the task will be completed because it depends on an external service which we do not have control over. Promise objects are a means to handle these problems asynchronously with an efficient API. Previously in JavaScript we used callbacks to handle asynchronous tasks however callbacks had many problems when it came to handling async tasks that depend on each other and hence promises was born.
Let's make a new Promise
const someTask = new Promise((resolve, reject) => {})
The easiest way to create a promise is to invoke new Promise
constructor. Let's look at an example
3 States of a Promise
- Pending - When you newly create a promise it will be pending
- Fulfilled / Resolved - When a promise has been resolved
- Rejected - When an error occured and the promised value can't be determined
So we instantiate a new Promise
and pass in a function with 2 arguments resolve
and reject
.What are resolve
and reject
one may ask. Essentially resolve
should be called when a task has been successfully fulfilled and reject
should be called if the task has failed. Let's take our example further. We will use setTimeout to simulate an async task that will take some time to complete. This task will basically take around 500ms to complete.
const calculation = new Promise((resolve, reject) => {
setTimeout(() => resolve(1 + 1), 500)
})
Essentially inside of a promise you call resolve when a task is completed. That's basically how the promise knows the task has been 'resolved'
Once you define a promise the next thing is you need to be able to get the result of that function. The following code will log the result to the console. In this case we're adding 1 + 1 so we'll get 2 returned to the console.
calculation.then(result => console.log(result))
=> 2
Rejecting a Result (Error Handling)
Let's say for example we have the following, we're trying to figure out if someone can have coffee or not.
const age = 7
const user = {name: 'zack'}
const checkCoffeeAble = new Promise((resolve, reject) => {
if (age >= 18) {
resolve(user)
} else {
reject("cannot have coffee")
}
})
const canHaveCoffee = (result) => {
localStorage('coffee_drinker', result.name)
}
const cannotHaveCoffee = (result) => {
console.log(result)
}
checkCoffeeAble.then(canHaveCoffee, cannotHaveCoffee)
In this case we're hard coding the age so we'll probably get a rejected result output to the console. But the point is you can see that we still handle rejections using then we simply pass the rejected function as a second argument of then. We can also use a catch like this.
checkCoffeeAble.then(canHaveCoffee).catch(cannotHaveCoffee)
Next we'll actually take a look at a real-life example where we will actually make a network call to determine some kind of a result with a basic logic.
A More Contrived Example
Let's say we want to be able to query the github for popular repo by topic name we pass in a topic name and the result will be rendered to the dom.So the basic flow will be something like this.
- Make a call to Github using fetch
- Get the data from github and iterate through it using map inside a Promise
- Render the results to the page.
- If there are no results we render an error message.
The reason we are doing it this way is
- We don't want to just use fetch as fetch already returns a Promise by default
- We want some kind of workload that can be solved with a new Promise
- Let's setTimeout and console.log can make a pretty boring example
We'll use a promise to loop through the results and generate the html which we will then use to update the dom. Here is the JS code and the codesandbox.
import "./styles.css";
const headers = {
Accept: "application/vnd.github.mercy-preview+json"
};
const getResults = topic =>
fetch(`https://api.github.com/search/topics?q=${topic}`, {
headers
})
.then(response => response.json())
.then(body => body.items);
const renderItems = items =>
new Promise((resolve, reject) => {
if (Array.isArray(items) && items.length > 0) {
const data = items.map(item => `<h2>${item.name}</h2>`).join("");
resolve(data);
} else {
reject("<div>no results found try a different topic</div>");
}
});
const searchTopics = topic => {
document.getElementById("app").innerHTML = "<h1>Loading...</h1>";
getResults(topic)
.then(renderItems)
.then(html => {
document.getElementById("app").innerHTML = `
<div class='results'>
<h1>results for: ${topic}</h1>
${html}
</div>
`;
})
.catch(error => {
document.getElementById("app").innerHTML = `
<div class='error'>${error}</div>
`;
});
};
// try passing in "89348598095380953" for error response
searchTopics("machine learning");
The main function using the custom Promise is here.
const renderItems = items =>
new Promise((resolve, reject) => {
if (items.length > 0) {
const data = items.map(item => `<h2>${item.name}</h2>`).join("");
resolve(data);
} else {
reject("<div>no results found try a different topic</div>");
}
});
Let's dissect it a little bit in the if statement we're checking that the network call actually returned a result. If nothing was returned we use reject if something was returned we iterate through and use resolve when the iteration is done.
Here is how it's called.
getResults(topic)
.then(renderItems)
.then(html => {
document.getElementById("app").innerHTML = `
<div class='results'>${html}</div>
`;
})
.catch(error => {
document.getElementById("app").innerHTML = `
<div class='error'>${error}</div>
`;
});
First we use getResults(topic) then we pass in our renderItems Promise as callback function then we handle the success scenario by rendering the results in a results div and if we don't have any results returned we catch and render the message in an error div.
Wrap Up
Promises might seem a bit daunting at first but overall opens up a better way to write asynchronous code without suffering from callback hell. This post is meant as a primer on promises, in the next post we will show you an alternative syntax (async/await) for Promises that make it even easier to write async code in JavaScript and we'll see how we can clean up our example using async/await.
If you have any questions / concerns about this post feel free to ask a question or give feedback here.