debian-mirror-gitlab/app/assets/javascripts/smart_interval.js

173 lines
4.6 KiB
JavaScript
Raw Normal View History

2017-09-10 17:25:29 +05:30
/**
* Instances of SmartInterval extend the functionality of `setInterval`, make it configurable
* and controllable by a public API.
*/
2018-03-17 18:26:18 +05:30
export default class SmartInterval {
2017-09-10 17:25:29 +05:30
/**
2018-03-17 18:26:18 +05:30
* @param { function } opts.callback Function that returns a promise, called on each iteration
* unless still in progress (required)
2017-09-10 17:25:29 +05:30
* @param { milliseconds } opts.startingInterval `currentInterval` is set to this initially
* @param { milliseconds } opts.maxInterval `currentInterval` will be incremented to this
* @param { milliseconds } opts.hiddenInterval `currentInterval` is set to this
* when the page is hidden
* @param { integer } opts.incrementByFactorOf `currentInterval` is incremented by this factor
* @param { boolean } opts.lazyStart Configure if timer is initialized on
* instantiation or lazily
* @param { boolean } opts.immediateExecution Configure if callback should
* be executed before the first interval.
*/
constructor(opts = {}) {
this.cfg = {
callback: opts.callback,
startingInterval: opts.startingInterval,
maxInterval: opts.maxInterval,
hiddenInterval: opts.hiddenInterval,
incrementByFactorOf: opts.incrementByFactorOf,
lazyStart: opts.lazyStart,
immediateExecution: opts.immediateExecution,
};
this.state = {
intervalId: null,
currentInterval: this.cfg.startingInterval,
pageVisibility: 'visible',
};
this.initInterval();
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
/* public */
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
start() {
const cfg = this.cfg;
const state = this.state;
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
if (cfg.immediateExecution && !this.isLoading) {
2017-09-10 17:25:29 +05:30
cfg.immediateExecution = false;
2018-03-17 18:26:18 +05:30
this.triggerCallback();
2017-08-17 22:00:37 +05:30
}
2017-09-10 17:25:29 +05:30
state.intervalId = window.setInterval(() => {
2018-03-17 18:26:18 +05:30
if (this.isLoading) {
return;
}
this.triggerCallback();
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
if (this.getCurrentInterval() === cfg.maxInterval) {
return;
2017-08-17 22:00:37 +05:30
}
2017-09-10 17:25:29 +05:30
this.incrementInterval();
this.resume();
}, this.getCurrentInterval());
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
// cancel the existing timer, setting the currentInterval back to startingInterval
cancel() {
this.setCurrentInterval(this.cfg.startingInterval);
this.stopTimer();
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
onVisibilityHidden() {
if (this.cfg.hiddenInterval) {
this.setCurrentInterval(this.cfg.hiddenInterval);
this.resume();
} else {
2017-08-17 22:00:37 +05:30
this.cancel();
}
2017-09-10 17:25:29 +05:30
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
// start a timer, using the existing interval
resume() {
2018-03-17 18:26:18 +05:30
this.stopTimer(); // stop existing timer, in case timer was not previously stopped
2017-09-10 17:25:29 +05:30
this.start();
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
onVisibilityVisible() {
this.cancel();
this.start();
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
destroy() {
this.cancel();
document.removeEventListener('visibilitychange', this.handleVisibilityChange);
$(document).off('visibilitychange').off('beforeunload');
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
/* private */
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
initInterval() {
const cfg = this.cfg;
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
if (!cfg.lazyStart) {
this.start();
2017-08-17 22:00:37 +05:30
}
2017-09-10 17:25:29 +05:30
this.initVisibilityChangeHandling();
this.initPageUnloadHandling();
}
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
triggerCallback() {
this.isLoading = true;
this.cfg.callback()
.then(() => {
this.isLoading = false;
})
.catch((err) => {
this.isLoading = false;
throw err;
});
}
2017-09-10 17:25:29 +05:30
initVisibilityChangeHandling() {
// cancel interval when tab no longer shown (prevents cached pages from polling)
document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
initPageUnloadHandling() {
// TODO: Consider refactoring in light of turbolinks removal.
// prevent interval continuing after page change, when kept in cache by Turbolinks
$(document).on('beforeunload', () => this.cancel());
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
handleVisibilityChange(e) {
this.state.pageVisibility = e.target.visibilityState;
const intervalAction = this.isPageVisible() ?
this.onVisibilityVisible :
this.onVisibilityHidden;
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
intervalAction.apply(this);
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
getCurrentInterval() {
return this.state.currentInterval;
}
setCurrentInterval(newInterval) {
this.state.currentInterval = newInterval;
}
incrementInterval() {
const cfg = this.cfg;
const currentInterval = this.getCurrentInterval();
if (cfg.hiddenInterval && !this.isPageVisible()) return;
let nextInterval = currentInterval * cfg.incrementByFactorOf;
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
if (nextInterval > cfg.maxInterval) {
nextInterval = cfg.maxInterval;
2017-08-17 22:00:37 +05:30
}
2017-09-10 17:25:29 +05:30
this.setCurrentInterval(nextInterval);
}
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
isPageVisible() { return this.state.pageVisibility === 'visible'; }
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
stopTimer() {
const state = this.state;
state.intervalId = window.clearInterval(state.intervalId);
2017-08-17 22:00:37 +05:30
}
2017-09-10 17:25:29 +05:30
}