Using Functions that return Promises
One of the great things about JavaScript is that it can run code asynchronously. Asynchronous code in JavaScript initially made use of callback functions that are triggered after a long running operation completes. But the problem with this approach is that it can lead to 'callback hell', which can make your code hard to read and understand (you can read about callback hell in this link)
Dealing with callbacks can be cumbersome and confusing. The 'Promise API' was introduced to make things easier. Promises were specifically designed run code that may take a long time to complete and/or may not succeed. A perfect scenario that calls for a Promise is when your code needs to make an HTTP request to fetch data from another server on the internet (also known as an AJAX call). The request could take a long time for any number of reasons (ex: if the network is busy) and it could ultimately fail (eg: the server may be down). In such cases, you can write a function that immediately returns a Promise object (rather than waiting for a callback to be triggered). The Promise object has a method called then() that you can call in order to handle a successful request, it also has a method called catch() that you can call in order to handle requests that fail.
In this lesson we are going to focus on using functions that return a promise rather than writing them. If you really want to learn how to write them, you can take a look at this article.
That being said, I'm going to give you a function that returns a Promise object, and I don't want you to worry about the code that's in it. Instead, you should focus on what you can do with the Promise object that it returns.
Create a web page called using-promises.html, and paste this code into it (again, do not worry about the code inside the function, all you need to know right now is that it returns a Promise object):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Using Promises</title>
<script>
// A function that could take a long time to complete, and could fail.
// So, it returns a Promise object
function getDog() {
return new Promise((resolve, reject) => {
setTimeout(() => {
let randomNumber = Math.round(Math.random());
if (randomNumber == 1) {
resolve({name:"Brutus", breed:"Boxer", age:9});
} else {
reject(new Error("Failed to get dog!"));
}
}, 3000);
});
}
// TODO: Put your code here
</script>
</head>
<body>
<h1>Using Promises</h1>
</body>
</html>
Now that we have a function that returns a Promise object, let's begin to explore how we can work with it.
The function will immediately return a Promise object, and then take 3 seconds to do it's work. After 3 seconds it may either succeed in getting the dog, or fail by throwing an error (success or failure depends on the random number that is generated). We can deal with the success and failure by calling then() and catch() on the Promise object:
const promise = getDog();
promise.then(/*success callback goes here*/).catch(/*error callback goes here*/);
Both then() and catch() require you to pass in a callback, which allows you to handle the success or failure scenarios.
Many programmers will not bother to declare a variable for the promise, instead they'll write the code like so:
getDog().then(/*success callback goes here*/).catch(/*error callback goes here*/);
This can result in a very long line of code, so it can be formatted it like so:
getDog()
.then(/*success callback goes here*/)
.catch(/*error callback goes here*/);
Now go ahead and put this code under the //TODO: comment in our sample page:
getDog()
.then(dog => {
console.log("Here's the dog:", dog)
})
.catch(err => console.log(err.message))
Load the page in the browser and wait 3 seconds, then reload the page a few times until you get both success and failure results.
Note that the arrow function we passed into then() has a parameter named 'dog'. This is what we are hoping to get from the promise. And in the body of this arrow function we can add code that works with the dog object.
Also note that the arrow function we passed into catch() has a param for the error object that is thrown when we are unable to get the dog. Catching an error like this prevents the page from crashing. If you comment out the line that calls catch() from this code sample, you'll see that if we are unable to get the dog, then the page will crash when the error is thrown.
You can call then() multiple times, like so (go ahead and copy this code into using-promises.html, just under the last code sample that we added):
// multiple then() calls
getDog()
.then(dog => {
console.log("Here's the dog:", dog);
return dog.name;
})
.then(dogName => {
console.log("Here's the dog name:", dogName);
})
.catch(err => console.log(err.message));
Reload the page until you see the console log that displays the dog name (this could take a few reloads).
The important thing to note here is that the value returned by the callback in the first then() becomes the parameter that is passed into the callback for the second then() call. This allows you to transform the result before for passing it down the chain of then() calls. This is a common practice when using the fetch() method to make AJAX calls (which we'll be doing in an upcoming activity).
async and await
Using then() and catch() may be a little easier than dealing asynchronous callbacks the old school way, but not much! So there is an alternative way of dealing with functions that return Promise objects, which allows your code to look like synchronous code. This is much easier to read and understand, and many programmers prefer it over using then() and catch().
In order for this approach to work, you must 'wrap' the call to the function that returns a promise inside another function. This outer function must be declared using the async keyword, like so:
const someWrapperFunction = async () => {
// call the function that returns the promise here
}
Further, when you call the function that returns the Promise object, you must use the await keyword, like so:
const someWrapperFunction = async () => {
const dog = await getDog();
console.log(dog)
}
Now this code looks as though getDog() immediately returns a dog object, as if it were a synchronous function. But the await keyword will force the rest of the code in the wrapper function to wait until getDog() completes it's work. So, if you were successfully able to get the dog, then the console log would be triggered after the 3 second delay. BTW, if you forget to include the await keyword, then the console log will be triggered immediately, without waiting for getDog() to complete it's work. And it will log a Promise object, because that's what getDog() returns.
You must use the async and await keywords together! Use async when declaring the wrapper function, and use await when calling the function that returns a Promise object.
The last code sample does not account for situations that fail. So you can deal with potential errors by wrapping the function call that returns a promise (which is getDog()) inside a try catch block like so:
const someWrapperFunction = async () => {
try{
const dog = await getDog();
console.log(dog);
}catch(err){
console.log(err.message);
}
}
Go ahead and copy this code sample into the sample file, just under the last code sample that you added. Then make sure to call the wrapper function after that:
someWrapperFunction();
Why do they call it a 'Promise'?
Apparently, when a friend promises to do something for you, they should let you know whether or not they were able to fulfill the promise. That way you don't have to wait forever to hear back from them.