gitlab-org--gitlab-foss/app/assets/javascripts/lib/utils/poll.js
Stan Hu ec5bbd27f5 Remove duplicate XHR request when requesting new pipeline page
When a user clicked on another page in the pipeline page, the following
would happen:

1. PipelinesService.getPipelines() would be called to fetch the new page
2. In the success handler, Poll.restart() would be called
3. This would invoke Poll.makeRequest(), which would fire off a new
request.

To fix this, we introduce a enable(), which will:

1. Update the request data accordingly
2. Clear the old timeout if necessary and start a new timer

Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/58095
2019-02-23 21:27:53 -08:00

129 lines
3.3 KiB
JavaScript

import httpStatusCodes, { successCodes } from './http_status';
import { normalizeHeaders } from './common_utils';
/**
* Polling utility for handling realtime updates.
* Requirements: Promise based HTTP client
*
* Service for promise based http client and method need to be provided as props
*
* @example
* new Poll({
* resource: resource,
* method: 'name',
* data: {page: 1, scope: 'all'}, // optional
* successCallback: () => {},
* errorCallback: () => {},
* notificationCallback: () => {}, // optional
* }).makeRequest();
*
* Usage in pipelines table with visibility lib:
*
* const poll = new Poll({
* resource: this.service,
* method: 'getPipelines',
* data: { page: pageNumber, scope },
* successCallback: this.successCallback,
* errorCallback: this.errorCallback,
* notificationCallback: this.updateLoading,
* });
*
* if (!Visibility.hidden()) {
* poll.makeRequest();
* }
*
* Visibility.change(() => {
* if (!Visibility.hidden()) {
* poll.restart();
* } else {
* poll.stop();
* }
* });
*
* 1. Checks for response and headers before start polling
* 2. Interval is provided by `Poll-Interval` header.
* 3. If `Poll-Interval` is -1, we stop polling
* 4. If HTTP response is 200, we poll.
* 5. If HTTP response is different from 200, we stop polling.
*
*/
export default class Poll {
constructor(options = {}) {
this.options = options;
this.options.data = options.data || {};
this.options.notificationCallback =
options.notificationCallback || function notificationCallback() {};
this.intervalHeader = 'POLL-INTERVAL';
this.timeoutID = null;
this.canPoll = true;
}
checkConditions(response) {
const headers = normalizeHeaders(response.headers);
const pollInterval = parseInt(headers[this.intervalHeader], 10);
if (pollInterval > 0 && successCodes.indexOf(response.status) !== -1 && this.canPoll) {
if (this.timeoutID) {
clearTimeout(this.timeoutID);
}
this.timeoutID = setTimeout(() => {
this.makeRequest();
}, pollInterval);
}
this.options.successCallback(response);
}
makeRequest() {
const { resource, method, data, errorCallback, notificationCallback } = this.options;
// It's called everytime a new request is made. Useful to update the status.
notificationCallback(true);
return resource[method](data)
.then(response => {
this.checkConditions(response);
notificationCallback(false);
})
.catch(error => {
notificationCallback(false);
if (error.status === httpStatusCodes.ABORTED) {
return;
}
errorCallback(error);
});
}
/**
* Stops the polling recursive chain
* and guarantees if the timeout is already running it won't make another request by
* cancelling the previously established timeout.
*/
stop() {
this.canPoll = false;
clearTimeout(this.timeoutID);
}
/**
* Enables polling after it has been stopped
*/
enable(options) {
if (options && options.data) {
this.options.data = options.data;
}
this.canPoll = true;
if (options && options.response) {
this.checkConditions(options.response);
}
}
/**
* Restarts polling after it has been stopped and makes a request
*/
restart(options) {
this.enable(options);
this.makeRequest();
}
}