Introduction
I wanted to write a shorti(ish) post mostly for myself about how to use async/await
on in the browser. It's one of those things that no matter how many often I use it, I feel like I have to start from scratch every time. Hopefully by writing this I'll clarify it for myself and perhaps others who have a similar problem.
Setup
I wanted to keep my example as straightforward as possible. To me, that means it should:
- have very few DOM elements
- use no frameworks
- use at most one interaction
The point isn't to be fancy or even pretty. The point is only to make sure I understand the concept. To do that I came up with the following idea.
The user should be able to click a button and have a random image display in the browser.
Excellent. One button tag, one image tag, and one click
event listener.
HTML
The markup for this is going to be very straightforward.
<div>
<img src='' />
<button>Get a random image</button>
</div>
Note that the image tag's source attribute is empty. That's what I'll fill in with javascript. Speaking of which...
Javascript
To get the image, I'm going to use unsplash's random image endpoint. It's perfect for this because
- it doesn't require an API key
- it will return an image url
- for now I don't care about anything else
Variables
First, I'll define a few variables.
const img = document.querySelector('img')
const button = document.querySelector('button')
const div = document.querySelector('div')
const url = 'https://source.unsplash.com/random'
Event Listener
Next, before I get really complicated, I like to write my event listener and log a click event. That way I know at least that works correctly before getting into the "hard part".
const img = document.querySelector('img')
const button = document.querySelector('button')
const div = document.querySelector('div')
const url = 'https://source.unsplash.com/random'
button.addEventListener('click', () => {
console.log('click');
});
async/await
Now comes the part I care about. What I want is to fetch the image asynchronously. Then, I want to use the source url of the result as the src
attribute of the image.
To do that, I want to write an async
function and have it return
the await
ed result. To get the response from the endpoint, I'll use the fetch
API.
Let's call the function getImage
.
const img = document.querySelector('img')
const button = document.querySelector('button')
const div = document.querySelector('div')
const url = 'https://source.unsplash.com/random'
const getImage = async (url) => {
return await fetch(url).then(res => res.url)
}
button.addEventListener('click', () => {
console.log('click');
});
Now when the button is clicked, the getImage
function can be run. But, that's the "tricky" part. It's not sufficient to just call the function. That will leave the Promise
returned by the function in a pending state. You have to do something with the result of the function. That's done by chaining a .then
function on to it. This is where I can set the attribute of the image tag.
const img = document.querySelector('img')
const button = document.querySelector('button')
const div = document.querySelector('div')
const url = 'https://source.unsplash.com/random'
const getImage = async (url) => {
return await fetch(url).then(res => res.url)
}
button.addEventListener('click', () => {
getImage(url)
.then(result => {
img.setAttribute('src', result)
})
});
Handling failure
The last thing that needs to happen for this little example is some kind of error handling. Right now if something goes wrong, the person clicking the button won't know. To handle that, I want to catch
the error, log it, and display a message.
const img = document.querySelector('img')
const button = document.querySelector('button')
const div = document.querySelector('div')
const url = 'https://source.unsplash.com/random'
const getImage = async (url) => {
return await fetch(url).then(res => res.url)
}
button.addEventListener('click', () => {
getImage(url)
.then(result => {
img.setAttribute('src', result)
})
.catch(err => {
// log the specific error
console.error(`Error -> ${err}`)
const errorMessage = 'Sorry, but there was an error getting your image.'
const p = document.createElement('p')
const container = document.querySelector('.container')
p.innerText = errorMessage;
//display an error message to the user
container.appendChild(p)
})
});
Conclusion
I like doing small examples like this. They're a good way for me to focus on the core problem I want to solve and not get distracted with other issues. Here, the main thing was to have a reminder for myself of how to create an asynchronous browser interaction and handle a successful or unsuccessful response without getting too wrapped up in intracacies.