Getting Sleep() with Promises in JS
noam sauer-utley
Posted on February 14, 2020
Note: this post was originally published on Medium in November, 2019
Recently, I started building a very basic single page app to illustrate Fetch API usage in a cute and simple way.
When the user clicks a button styled like a tennis ball, a new dog image is fetched from the Dog API, and given a name fetched from the Namey API.
The dog’s image is loaded onto the page, and with each fetch, a name is added onto a list of fetched dogs.
However, once a few dogs were fetched, the document content stretched down quite far, and required a bit of scrolling to view. I decided to add a handy little link in the page footer which would scroll the viewer right up to the top, and re-fire the fetch cycle.
function scrollAndThrow(){
//smooth scroll back to top left corner of window
window.scroll({
top: 0,
left: 0,
behavior: 'smooth'
})
// then click the button to trigger a new round of fetch
button.click()
}
We can just scroll up the window, then trigger a newclick event on the button we’ve assigned to the variable button.
However, the the combo of the page scrolling up and the image fetch simultaneously re-firing looks pretty messy.
I wanted a way to slow things down, so the user would only see one thing happening on the page at a time.
I knew that if I could just pause for half a second between the scroll and the API Fetch, everything would look much smoother.
Suddenly, I missed Ruby’s sleep method. It was so nice to be able to schedule events to the exact time I wanted them to run!
I decided there had to be a way to construct my own dupe of that handy little method.
If you’re not familiar with JS Promises, it’s important to first understand that while Javascript is single-threaded (meaning it can only process one statement at a time), it is also asynchronous (meaning it can start processing a new statement before the previous statement has resolved, allowing it to multitask time-consuming processes in the background.).
Javascript uses Promises to track the resolution of those background processes, allowing us to set certain statements to run only when a time-consuming process has resolved and returned its completed result.
This is great for handling fetch requests and other time consuming processes, but it also gives us a way to tell our app to wait to process certain statements until exactly when we want them to be run.
I realized that I could exploit this functionality by building a Promise around Javascript’s built-in setTimeout() method.
Let’s make a promise:
setTimeout() takes in two parameters:
1: A function to be executed after the timer expires.
2: The time, in milliseconds (thousandths of a second), the timer should wait before the specified function or code is executed. If this argument is omitted, a value of 0 is used, meaning execute “immediately”, or more accurately, as soon as possible. Note that in either case, the actual delay may be longer than intended
return new Promise(resolve => setTimeout(resolve, ms))
We can create a new Promise, and tell setTimeout to pass the Promise resolve statement as setTimeout’s first parameter. setTimeout will delay for ms milliseconds, then resolve the promise. If we throw this in a function, we now have a handle little delay function ready to go!
function sleep(ms) {
// add ms millisecond timeout before promise resolution
return new Promise(resolve => setTimeout(resolve, ms))
}
Great! Now we made our replacement for Ruby’s sleep method.
Now let’s put it to use.
I decided to take advantage of one of ES6’s fresher bits of syntax for Promise handling: Async/await.
async function delayedClick(){
// await sleep function promise
await sleep(700)
// once resolved, click button
button.click()
}
delayedClick() knows from the get that it’s awaiting a promise resolution. We’re using our brand new sleep() function as the awaited promise, passing in our desired number of milliseconds (in this case, 700ms , or 7/10ths of a second ). Once that promise resolves, we can enter the action we want delayedClick() to enact. In this case, we’re going to click the button that re-starts the fetch and display cycle.
Now that we have our delay and desired action plugged into a method, we can plug delayedClick() into our existing scrollAndThrow() method as a replacement to button.click() to slow things down and clean up the onscreen process.
function scrollAndThrow(){
// smooth scroll back to top left corner of window
window.scroll({
top: 0,
left: 0,
behavior: 'smooth'
})
// use sleep function promise to initiate 700 millisecond delay
// then click button and initiate new fetch cycle
delayedClick()
}
The result: no more jaggedy mid-scroll fetch return and image load!
Sometimes, it’s just good to get some sleep!
Notes:
- Want to fetch a dog? Check out the repo here.
- For dog pics, I used the Dog API: https://dog.ceo/dog-api/ .
- for random first names, I used the muffinlabs Namey API: https://namey.muffinlabs.com/
- Using Node.js? Turns out that there’s a handy little module built by Wes Bos that keeps this functionality ready to go for you. Check out Waait: https://www.npmjs.com/package/waait.
Posted on February 14, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.