Skip to content
57 changes: 54 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
* @property {Array<(body: any, headers: Headers) => any?>} [transformRequest] An array of transformations to apply to the outgoing request
* @property {string} [baseURL] a base URL from which to resolve all URLs
* @property {typeof window.fetch} [fetch] Custom window.fetch implementation
* @property {AbortSignal} [cancelToken] signal returned by AbortController
* @property {any} [data]
*/

Expand Down Expand Up @@ -64,6 +65,16 @@
* @type {<T=any>(url: string, body?: any, config?: Options) => Promise<Response<T>>}
*/

/**
* @typedef CancelToken
* @type {{ (executor: Function): AbortSignal; source(): { token: AbortSignal; cancel: () => void; }; }}
*/

/**
* @typedef CancelTokenSourceMethod
* @type {() => { token: AbortSignal, cancel: () => void }}
*/

/** */
export default (function create(/** @type {Options} */ defaults) {
defaults = defaults || {};
Expand Down Expand Up @@ -138,6 +149,38 @@ export default (function create(/** @type {Options} */ defaults) {
return out;
}

/**
* CancelToken
* @private
* @param {Function} executor
* @returns {AbortSignal}
*/
function CancelToken(executor) {
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}

const ac = new AbortController();

executor(ac.abort.bind(ac));

return ac.signal;
}

/**
* @private
* @type {CancelTokenSourceMethod}
* @returns
*/
CancelToken.source = () => {
const ac = new AbortController();

return {
token: ac.signal,
cancel: ac.abort.bind(ac)
};
};

/**
* Issues a request.
* @public
Expand Down Expand Up @@ -199,7 +242,8 @@ export default (function create(/** @type {Options} */ defaults) {
method: _method || options.method,
body: data,
headers: deepMerge(options.headers, customHeaders, true),
credentials: options.withCredentials ? 'include' : 'same-origin'
credentials: options.withCredentials ? 'include' : 'same-origin',
signal: options.cancelToken
}).then((res) => {
for (const i in res) {
if (typeof res[i] != 'function') response[i] = res[i];
Expand All @@ -225,9 +269,16 @@ export default (function create(/** @type {Options} */ defaults) {

/**
* @public
* @type {AbortController}
* @type {CancelToken}
*/
redaxios.CancelToken = CancelToken;

/**
* @public
* @param {DOMError} e
* @returns {boolean}
*/
redaxios.CancelToken = /** @type {any} */ (typeof AbortController == 'function' ? AbortController : Object);
redaxios.isCancel = (e) => e.name === 'AbortError';

/**
* @public
Expand Down
49 changes: 49 additions & 0 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -318,4 +318,53 @@ describe('redaxios', () => {
expect(result).toEqual('hello world');
});
});

describe('Request cancellation using options.cancelToken', () => {
it('should cancel a request when cancelToken is passed as source.token', async () => {
const CancelToken = axios.CancelToken;
const source = CancelToken.source();

const axiosGet = axios.get(jsonExample, {
cancelToken: source.token
});
source.cancel();

const spy = jasmine.createSpy();
await axiosGet.catch(spy);

expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(
jasmine.objectContaining({ code: 20, message: 'The user aborted a request.', name: 'AbortError' })
);
});

it('should cancel a request when cancelToken is passed as instance CreateToken', async () => {
const CancelToken = axios.CancelToken;
let cancel;

const axiosGet = axios.get(jsonExample, {
cancelToken: new CancelToken(function executor(c) {
cancel = c;
})
});

cancel();

const spy = jasmine.createSpy();
let error;
await axiosGet.catch((e) => ((error = e), spy(e)));

expect(axios.isCancel(error)).toBeTruthy(true);
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(
jasmine.objectContaining({ code: 20, message: 'The user aborted a request.', name: 'AbortError' })
);
});

it('should throw TypeError if no executor function is passed to CancelToken constructor', () => {
const CancelToken = axios.CancelToken;

expect(() => new CancelToken()).toThrowError('executor must be a function.');
});
});
});