UNPKG

axios-ex

Version:

A integration solution of axios for large enterprise projects

364 lines (358 loc) 12.3 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var tslib = require('tslib'); var axios = require('axios'); var isRetryAllowed = require('is-retry-allowed'); var jsCool = require('js-cool'); var SAFE_HTTP_METHODS = ['get', 'head', 'options']; var IDEMPOTENT_HTTP_METHODS = SAFE_HTTP_METHODS.concat(['put', 'delete']); /** * Get the default delay time in milliseconds * * @private * @returns number - delay in milliseconds, always 0 */ function noRetryDelay() { return 0; } /** * the config for retry when initialize and return * * @param config - AxiosExtendRequestOptions * @return currentState */ function getCurrentState(config) { var currentState = config['axios-extend'] || {}; currentState.retryCount = currentState.retryCount || 0; config['axios-extend'] = currentState; return currentState; } /** * Get the request data * * @param config - AxiosExtendRequestOptions * @param defaultOptions - AxiosExtendConfig * @return options */ function getRequestOptions(config, defaultOptions) { return Object.assign({}, defaultOptions, config['axios-extend']); } /** * Clean up agent to prevent dead loops * * @param axios - any * @param config - any */ function fixConfig(axios, config) { if (axios.defaults.agent === config.agent) { delete config.agent; } if (axios.defaults.httpAgent === config.httpAgent) { delete config.httpAgent; } if (axios.defaults.httpsAgent === config.httpsAgent) { delete config.httpsAgent; } } /** * @param error - Error * @return boolean */ function isNetworkError(error) { return !error.response && Boolean(error.code) && // Prevents retrying cancelled requests error.code !== 'ECONNABORTED' && // Prevents retrying timed out requests isRetryAllowed(error); // Prevents retrying unsafe errors } /** * @param error - Error * @return boolean */ function isSafeRequestError(error) { // Cannot determine if the request can be retried if (!error.config) return false; return isRetryableError(error) && SAFE_HTTP_METHODS.includes(error.config.method); } /** * @param error - Error * @return boolean */ function isIdempotentRequestError(error) { // Cannot determine if the request can be retried if (!error.config) return false; return isRetryableError(error) && IDEMPOTENT_HTTP_METHODS.includes(error.config.method); } /** * @param error - Error * @return boolean */ function isNetworkOrIdempotentRequestError(error) { return isNetworkError(error) || isIdempotentRequestError(error); } /** * @param retryNumber - default: 0 * @return delay milliseconds */ function exponentialDelay(retryNumber) { if (retryNumber === void 0) { retryNumber = 0; } var delay = Math.pow(2, retryNumber) * 1000; var randomSum = delay * 0.5 * Math.random(); // 0-50% of the delay return delay + randomSum; } /** * @param error - Error * @return boolean */ function isRetryableError(error) { return error.code !== 'ECONNABORTED' && (!error.response || error.response.status >= 500 && error.response.status <= 599); } /** * AxiosExtend class * * @return Promise */ var AxiosExtend = /** @class */function () { function AxiosExtend(_a) { var orderly = _a.orderly, unique = _a.unique, retries = _a.retries, onCancel = _a.onCancel, defaultOptions = tslib.__rest(_a, ["orderly", "unique", "retries", "onCancel"]); this.axiosInstance = null; this.waiting = {}; // Request Queue this.unique = false; // Whether to cancel the previous similar requests, default: false this.orderly = true; // Whether to return in order, default: true this.onCancel = null; // Callback when request is cancelled this.orderly = orderly !== null && orderly !== void 0 ? orderly : true; this.unique = unique !== null && unique !== void 0 ? unique : false; this.retries = retries !== null && retries !== void 0 ? retries : 0; this.onCancel = onCancel !== null && onCancel !== void 0 ? onCancel : null; // Initialization method this.init(defaultOptions); return this; } /** * Initialization */ AxiosExtend.prototype.init = function (defaultOptions) { var _this = this; var setHeaders = defaultOptions.setHeaders, onRequest = defaultOptions.onRequest, onRequestError = defaultOptions.onRequestError, onResponse = defaultOptions.onResponse, onResponseError = defaultOptions.onResponseError, onError = defaultOptions.onError, options = tslib.__rest(defaultOptions, ["setHeaders", "onRequest", "onRequestError", "onResponse", "onResponseError", "onError"]); if (!this.axiosInstance) this.axiosInstance = axios.create(options); // Set request headers setHeaders && setHeaders(axios); // Adding a request interceptor onRequest && axios.interceptors.request.use(function (config) { var currentState = getCurrentState(config); currentState.lastRequestTime = Date.now(); if (currentState.retryCount > 0) return config; // retry re-requests the interface without executing onRequest again return onRequest(config, config.requestOptions); }, function (err) { onRequestError && onRequestError(err); onError && onError(err); return Promise.reject(err); }); // Adding a response interceptor onResponse && axios.interceptors.response.use(function (res) { return onResponse(res, res.config.requestOptions); }, function (err) { var config = err.config; // No request config if (!config) { onResponseError && onResponseError(err); onError && onError(err); return Promise.reject(err); } var _a = getRequestOptions(config, defaultOptions), _b = _a.retries, retries = _b === void 0 ? _this.retries : _b, _c = _a.retryCondition, retryCondition = _c === void 0 ? isNetworkOrIdempotentRequestError : _c, _d = _a.retryDelay, retryDelay = _d === void 0 ? noRetryDelay : _d, _e = _a.shouldResetTimeout, shouldResetTimeout = _e === void 0 ? false : _e; var currentState = getCurrentState(config); var shouldRetry = retryCondition(err) && currentState.retryCount < retries; if (shouldRetry) { currentState.retryCount += 1; var delay_1 = retryDelay(currentState.retryCount, err); // Clean up agent to prevent dead loops fixConfig(axios, config); if (!shouldResetTimeout && config.timeout && currentState.lastRequestTime) { var lastRequestDuration = Date.now() - currentState.lastRequestTime; // Minimum 1ms timeout (passing 0 or less to XHR means no timeout) config.timeout = Math.max(config.timeout - lastRequestDuration - delay_1, 1); } // Initialize request data config.transformRequest = [function (data) { return data; }]; return new Promise(function (resolve) { return setTimeout(function () { return resolve(axios(config)); }, delay_1); }); } onResponseError && onResponseError(err); onError && onError(err); return Promise.reject(err); }); }; /** * Create request */ AxiosExtend.prototype.create = function ( // url: string | AxiosExtendRequestOptions<D>, config) { var _this = this; var _a = config.unique, unique = _a === void 0 ? this.unique : _a, _b = config.orderly, orderly = _b === void 0 ? this.orderly : _b, _c = config.url, url = _c === void 0 ? '' : _c; var promiseKey = Symbol('promiseKey'); var source = axios.CancelToken.source(); var abortController; config.requestOptions = jsCool.extend(true, {}, config); config.cancelToken = source.token; if (typeof AbortController === 'function') { abortController = new AbortController(); config.signal = abortController.signal; } // Interface must return in order or need to cancel url same request unique && this.clear(url); var promise = new Promise(function (resolve, reject) { axios(config).then(function (res) { if (!orderly) resolve(res);else _this.wait(url, promiseKey).then(function () { resolve(res); }); }).catch(function (err) { // Request cancelled if (axios.isCancel(err)) _this.onCancel && _this.onCancel(err); // Request error else reject(err); }).finally(function () { var index = _this.waiting[url].findIndex(function (el) { return el.promiseKey === promiseKey; }); index > -1 && _this.waiting[url].splice(index, 1); }); }); this.add(url, { promiseKey: promiseKey, url: url, promise: promise, source: source, abortController: abortController }); return promise; }; /** * Drop all un-need requests * * @param key - the key of waiting line, usually to be the request url */ AxiosExtend.prototype.clear = function (key) { var e_1, _a; for (var url in this.waiting) { // no key => clean all if (!key || url === key) { var waitingList = this.waiting[url] || []; try { for (var waitingList_1 = (e_1 = void 0, tslib.__values(waitingList)), waitingList_1_1 = waitingList_1.next(); !waitingList_1_1.done; waitingList_1_1 = waitingList_1.next()) { var item = waitingList_1_1.value; item.source.cancel('request canceled'); item.abortController && item.abortController.abort(); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (waitingList_1_1 && !waitingList_1_1.done && (_a = waitingList_1.return)) _a.call(waitingList_1); } finally { if (e_1) throw e_1.error; } } this.waiting[url] = []; } } }; /** * Waiting to resolve the item before this request * * @param key - the key of waiting line, usually to be the request url * @param promiseKey - the unique promise key * @returns - Promise<void> */ AxiosExtend.prototype.wait = function (key, promiseKey) { return tslib.__awaiter(this, void 0, void 0, function () { var waitingList, index; return tslib.__generator(this, function (_b) { switch (_b.label) { case 0: if (!this.orderly) return [2 /*return*/, Promise.resolve()]; waitingList = this.waiting[key] || []; index = waitingList.findIndex(function (item) { return item.promiseKey === promiseKey; }); _b.label = 1; case 1: if (!(index > 0)) return [3 /*break*/, 7]; index--; if (!(waitingList[index] && waitingList[index].promiseKey !== promiseKey)) return [3 /*break*/, 6]; _b.label = 2; case 2: _b.trys.push([2, 4,, 5]); return [4 /*yield*/, waitingList[index].promise // await waitingList.splice(index, 1)[0].promise ]; case 3: _b.sent(); return [3 /*break*/, 5]; case 4: _b.sent(); console.info('The task has been dropped'); return [3 /*break*/, 5]; case 5: waitingList.splice(index, 1); _b.label = 6; case 6: return [3 /*break*/, 1]; case 7: return [2 /*return*/]; } }); }); }; /** * set item to waiting list * * @param key - the key of waiting line, usually to be the request url * @param item - waiting object */ AxiosExtend.prototype.add = function (key, item) { if (!(key in this.waiting)) this.waiting[key] = []; this.waiting[key].push(item); }; return AxiosExtend; }(); exports.AxiosExtend = AxiosExtend; exports.default = AxiosExtend; exports.exponentialDelay = exponentialDelay; exports.isIdempotentRequestError = isIdempotentRequestError; exports.isNetworkError = isNetworkError; exports.isNetworkOrIdempotentRequestError = isNetworkOrIdempotentRequestError; exports.isRetryableError = isRetryableError; exports.isSafeRequestError = isSafeRequestError;