marmelab/battery-friendly-timer

Name: battery-friendly-timer

Owner: marmelab

Description: Mobile applications using setInterval to poll a server are a battery hogs. Save battery life by fetching data at the right moment.

Created: 2016-08-26 08:46:00.0

Updated: 2017-07-21 04:55:11.0

Pushed: 2017-01-12 08:58:26.0

Homepage: null

Size: 7

Language: JavaScript

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

Battery-Friendly Timer

Mobile applications using setInterval to poll a server are a battery hogs. Save battery life by fetching data at the right moment.

Motivation

Using AJAX polling on mobile is a very bad idea, because it drains the battery extremely fast. But why is that exactly?

Mobile devices use radio transmitters to communicate via 3G/4G, and their radio components are power hungry. Network providers and phone manufacturers have invented an escalation protocol to save battery life, called Radio Resource Control (RRC). Most of the time, a mobile device is idle, and uses low-power mode. When the user asks for an HTTP resource, the mobile device switches to high-power mode, downloads the resource, then switches back to low-power mode after a few seconds. The process of escalating to high power mode implies asking the radio tower for a dedicated channel and bandwidth allocation, and takes time and energy.

Therefore, mobile data network are optimized for burst: it's better to download all the data you need within a few seconds, then return to low-power mode. AJAX polling prevents the return to the low power mode, and keeps the device in high power mode until the battery is drained - even if it's only to download a few dozen bytes every minute or so.

Instead of polling a server at regular interval, you'd better call it when a download is already occurring. In that case, you know the device is in high power mode, and it's the ideal time to use the network.

The Battery-Friendly Timer listens to AJAX calls, and then triggers timeouts and intervals.

Usage

Usage is similar to setInterval, except you need to pass two delays instead of just one:

rt timer from 'battery-friendly-timer';

r.setInterval(
() => fetch('http://my.api.url/').then(/* ... */),
60 * 1000, // tryDelay: one minute
60 * 60 * 1000 // forceDelay: one hour

setInterval takes two delays:

In the previous example, the server is polled every 60 seconds if there is an active HTTP connexion. If not, the server is polled every hour.

The Timer object provides setTimeout, clearTimeout, setInterval, and clearInterval methods. Apart from the forceDelay argument, the signature of these methods is the same as the window methods.

Example: Suggest refresh after code update of a Single-Page-Application

This scripts displays a banner inviting the user to reload the application if the JS code has changed on the server side:

 id="update-available" style="position: absolute; top: 10px; right: 10px; padding: 1em; background-color: bisque; border-radius: 5px; display: none;">
Myapp has a new version.
<a href="#" onClick="window.location.reload(true);return false;">Click to reload</a>
v>
s
rt timer from 'battery-friendly-timer';

previousHtml;
r.setInterval(
() => fetch('http://my.app.url/index.html')
    .then(response => {
        if (response.status !== 200) {
            throw new Error('offline');
        }
        return response.text();
    })
    .then(html => {
        if (!previousHtml) {
            previousHtml = html;
            return;
        }
        if (previousHtml !== html) {
            previousHtml = html;
            document.getElementById('update-available').style.display = 'block';
        }
    })
    .catch(err => { /* do nothing */ }),
5 * 60 * 1000, // tryDelay: 5 minutes
24 * 60 * 60 * 1000 // forceDelay: 1 day

This works if you use Webpack, because the index.html is small, and includes a different cache buster param for the JS script each time you deploy. For instance, here is a typical index.html generated by Webpack:

CTYPE html>
l>
<head>
    <title>MyApp</title>
</head>
<body>
    <div id="root"></div>
    <script type="text/javascript" src="/main.js?184c0441d4c89b34ba08"></script>
</body>
ml>

In that case, comparing the HTML source is a fast and easy way to detect changes in the JS code.

If the HTML is bigger, instead of comparing the HTML source, you can compare a hash of the source. See for instance http://stackoverflow.com/a/7616484/1333479 for a fast and simple JS hash function for strings.

FAQ
Why not use Service Workers to intercept fetch()?

Because Service Workers are restricted to HTTPS, and require a more complex setup (loading an additional script).

Why not use push notifications?

If you can use push notifications (e.g. in a hybrid app), by all means do so. It's the best compromise in terms of reactivity and battery life. But web apps don't have an easy access to push notifications, and AJAX polling is the usual fallback.

Does it work if I use XHR instead of fetch()?

No, the timer only listens to fetch() calls. If you're still using XHR in a single-page application, it's time to make the switch.


This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.