src/request.js
/** * Imports ***/
import axios, { AxiosResponse, AxiosRequestConfig } from 'axios';
import Collection from './cj';
import RequestException from './exception';
/**
* Http request object.
*/
export default class Request {
/**
* Constructor
*
* @param {Object} auth - authentication object
* @param {string} auth.token - authentication token
* @param {string} contentType - request content type
* @param {number} [timeout=30000] - request timeout
*/
constructor(auth, contentType, timeout = 30000) {
/** @type {Object} */
this.auth = auth;
/** @type {string} */
this.contentType = contentType;
/** @type {number} */
this.timeout = timeout;
}
/**
* Perform a GET request.
*
* @param {string} url - url of the resource
* @param {?Object} params - search parameters
*
* @return {Promise<AxiosResponse>} - JS Promise, resolves to an ``axios reponse`` object
*/
get(url, params = null) {
const config = this._getConfig(url, 'get');
if (params) {
config.params = params;
}
return Request._callAxios(config);
}
/**
* Perform a POST request.
*
* @param {string} url - url of the resource
* @param {Object} data - JSON data object
* @param {?Object} uploadFileObj - custom object with a property with the same name as
* the API descriptor corresponding to the file and whose value is the file blob
*
* @return {Promise<AxiosResponse>} - JS Promise, resolves to an ``axios reponse`` object
*/
post(url, data, uploadFileObj = null) {
return this._postOrPut('post', url, data, uploadFileObj);
}
/**
* Perform a PUT request.
*
* @param {string} url - url of the resource
* @param {Object} data - JSON data object
* @param {?Object} uploadFileObj - custom object with a property with the same name as
* the API descriptor corresponding to the file and whose value is the file blob
*
* @return {Promise<AxiosResponse>} - JS Promise, resolves to an ``axios reponse`` object
*/
put(url, data, uploadFileObj = null) {
return this._postOrPut('put', url, data, uploadFileObj);
}
/**
* Perform a DELETE request.
*
* @param {string} url - url of the resource
*
* @return {Promise<AxiosResponse>} - JS Promise, resolves to an ``axios reponse`` object
*/
delete(url) {
const config = this._getConfig(url, 'delete');
return Request._callAxios(config);
}
/**
* Internal method to make either a POST or PUT request.
*
* @param {string} requestMethod - either 'post' or 'put'
* @param {string} url - url of the resource
* @param {Object} data - JSON data object
* @param {?Object} uploadFileObj - custom object with a property with the same name as
* the API descriptor corresponding to the file and whose value is the file blob
*
* @return {Promise<AxiosResponse>} - JS Promise, resolves to an ``axios reponse`` object
*/
_postOrPut(requestMethod, url, data, uploadFileObj = null) {
const config = this._getConfig(url, requestMethod);
config.data = data;
if (uploadFileObj) {
config['headers']['Content-Type'] = 'multipart/form-data';
const bFormData = new FormData();
for (let property in data) {
if (data.hasOwnProperty(property)) {
bFormData.set(property, data[property]);
}
}
for (let property in uploadFileObj) {
if (uploadFileObj.hasOwnProperty(property)) {
bFormData.set(property, uploadFileObj[property]);
}
}
config.data = bFormData;
}
return Request._callAxios(config);
}
/**
* Internal method to create a config file for axios.
*
* @param {string} url - url of the resource
* @param {string} method - request verb
*
* @return {AxiosRequestConfig} - axios configuration object
*/
_getConfig(url, method) {
const config = {
url: url,
method: method,
headers: {
Accept: this.contentType,
'Content-Type': this.contentType,
},
timeout: this.timeout,
};
if (this.auth && this.auth.username && this.auth.password) {
config.auth = this.auth;
} else if (this.auth && this.auth.token) {
config.headers.Authorization = 'Token ' + this.auth.token;
}
if (this.contentType === 'application/octet-stream') {
config.responseType = 'blob';
}
return config;
}
/**
* Internal method to make an axios request.
*
* @param {AxiosRequestConfig} config - axios configuration object
*
* @return {Promise<AxiosResponse>} - JS Promise, resolves to an ``axios reponse`` object
*/
static _callAxios(config) {
return axios(config)
.then(response => {
return response;
})
.catch(error => {
Request._handleRequestError(error);
});
}
/**
* Internal method to handle errors produced by HTTP requests.
*
* @param {Object} error - axios error object
*
* @throws {RequestException} throw error
*/
static _handleRequestError(error) {
let apiError;
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
//console.log(error.response.data);
//console.log(error.response.status);
//console.log(error.response.headers);
let errMsg = 'Bad server response!';
if (error.response.data.collection) {
errMsg = Collection.getErrorMessage(error.response.data.collection);
}
apiError = new RequestException(errMsg);
apiError.request = error.request;
apiError.response = error.response;
try {
apiError.response.data = JSON.parse(errMsg);
} catch (ex) {
apiError.response.data = errMsg;
}
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
//console.log(error.request);
apiError = new RequestException('No server response!');
apiError.request = error.request;
} else {
// Something happened in setting up the request that triggered an Error
//console.log('Error', error.message);
apiError = new RequestException(error.message);
}
throw apiError;
//console.log(error.config);
}
/**
* Helper method to run an asynchronous task defined by a task generator function.
*
* @param {function*()} taskGenerator - generator function
*/
static runAsyncTask(taskGenerator) {
// create the iterator
let task = taskGenerator();
// start the task
let result = task.next();
// recursive function to iterate through
(function step() {
// if there's more to do (result.value and result.done are iterator's properties)
if (!result.done) {
result.value
.then(resp => {
result = task.next(resp); // send this resp value to the yield
step();
})
.catch(error => {
result = task.throw(error); // throws error within taskGenerator generator
step();
});
}
})(); // start the recursive process by calling it immediatly
}
}