[Node.js] Polling
Polling with setTimeout
async function getNewMsgs() {
let json;
try {
const res = await fetch("/poll");
json = await res.json();
} catch (e) {
// back off code would go here
console.error("polling error", e);
}
allChat = json.msg;
render();
setTimeout(getNewMsgs, INTERVAL);
}
// just notice this is the last line of the doc
getNewMsgs();
So calling setTimeout recursivly. It is much better than setInterval().
Because network request might take a long time to give response, using setInterval might overload the server.
Calling setTimeout recusivly can wait request response, then make next call.
Polling with requestAnimationFrame
What if a user unfocuses the window? Either focuses another tab or opens Spotify. Do you want to still be making requests in the background? That's a valid product question and one you'd need to make a deliberate answer to. If your user is trying to buy concert tickets and they're going to sell out then you absolutely should run in the background to give your user the best shot of getting the tickets. If it's a realtime weather app, it's probably okay to pause your polling and wait for the user to refocus again so we don't waste data and resources of the user's device, especially if they're just going to close the tab and never look at it again. I don't know about you, but lots of people have 10 million Chrome tabs open at once and it could waste a lot of data if they're all doing polling in the background.
If you want to not pause when unfocused, setTimeout is a good way. If you do want to pause, requestAnimationFrame will automatically pause when the window isn't in use. In general, when on the fence, I'd prefer the latter as a better implementation.
HTML5 has page visibility API, requestAnimationFrame utilitize that API
// delete the following line from getNewMessage
setTimeout(getNewMsgs, INTERVAL);
// replace the final getNewMesgs call at the bottom
let timeToMakeNextRequest = 0;
async function rafTimer(time) {
if (timeToMakeNextRequest <= time) {
await getNewMsgs();
timeToMakeNextRequest = time + INTERVAL;
}
requestAnimationFrame(rafTimer);
}
requestAnimationFrame(rafTimer);
Backoff and Retry
What is a polling request fails? You don't want to thundering-herd yourself by hammering your own API with more requests, but you also want the user to get back in once it's not failing anymore. We'll look at strategies to mitigate that.
So on an API failure, we should immediately try again to see if we can recover. Assuming it goes well and we get a 200 OK response, then all is well and we continue polling as normal. Okay, but what if it fails again? Well, here you can try several strategies of how to do the math but the idea is you wait increasing intervals. First try again after 10 seconds, then 20, then 30, then 40, etc. Or you could exponential backoff and wait 2, 4, 8, 16, 32, 64, 128, etc. You'll have to choose a backoff strategy that works best for your usecase depending on vital is it to recover immediately and how difficult it will be for your servers to recover.
// replace getNewMsgs
async function getNewMsgs() {
try {
const res = await fetch("/poll");
const json = await res.json();
if (res.status >= 400) {
throw new Error("request did not succeed: " + res.status);
}
allChat = json.msg;
render();
failedTries = 0;
} catch (e) {
// back off
failedTries++;
}
}
// replace at bottom
const BACKOFF = 5000;
let timeToMakeNextRequest = 0;
let failedTries = 0;
async function rafTimer(time) {
if (timeToMakeNextRequest <= time) {
await getNewMsgs();
timeToMakeNextRequest = time + INTERVAL + failedTries * BACKOFF;
}
requestAnimationFrame(rafTimer);
}
requestAnimationFrame(rafTimer);