diff --git a/src/index.js b/src/index.js index 39f523b..0b7ff9a 100644 --- a/src/index.js +++ b/src/index.js @@ -11,6 +11,8 @@ * limitations under the License. */ +import Interceptor from './interceptor'; + /** * @public * @typedef Options @@ -110,6 +112,12 @@ export default (function create(/** @type {Options} */ defaults) { // 3b smaller: // redaxios.spread = (fn) => /** @type {any} */ (fn.apply.bind(fn, fn)); + /** @public */ + redaxios.interceptors = { + request: new Interceptor(), + response: new Interceptor() + }; + /** * @private * @param {Record} opts @@ -151,16 +159,25 @@ export default (function create(/** @type {Options} */ defaults) { url = config.url; } - const response = /** @type {Response} */ ({ config }); + let response = /** @type {Response} */ ({ config }); /** @type {Options} */ - const options = deepMerge(defaults, config); + let options = deepMerge(defaults, config); + + if (_data) options.data = _data; + + redaxios.interceptors.request.handlers.map((handler) => { + if (handler) { + const resultConfig = handler.done(options); + options = deepMerge(options, resultConfig || {}); + } + }); + + let data = options.data; /** @type {Headers} */ const customHeaders = {}; - let data = _data || options.data; - (options.transformRequest || []).map((f) => { data = f(data, options.headers) || data; }); @@ -202,21 +219,33 @@ export default (function create(/** @type {Options} */ defaults) { } const ok = options.validateStatus ? options.validateStatus(res.status) : res.ok; - if (!ok) return Promise.reject(response); - - if (options.responseType == 'stream') { - response.data = res.body; - return response; + if (!ok) { + redaxios.interceptors.response.handlers.map((handler) => { + if (handler && handler.error) { + handler.error(res); + } + }); + const error = Promise.reject(response); + redaxios.interceptors.request.handlers.map((handler) => { + if (handler && handler.error) { + handler.error(error); + } + }); + return error; } return res[options.responseType || 'text']() .then((data) => { response.data = data; - // its okay if this fails: response.data will be the unparsed value: - response.data = JSON.parse(data); + try { + response.data = JSON.parse(data); + } + catch (e) {} + redaxios.interceptors.response.handlers.map((handler) => { + response = (handler && handler.done(response)) || response; + }); + return response; }) - .catch(Object) - .then(() => response); }); } diff --git a/src/interceptor.js b/src/interceptor.js new file mode 100644 index 0000000..85de121 --- /dev/null +++ b/src/interceptor.js @@ -0,0 +1,45 @@ +/** + * Copyright 2018 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +function Interceptor() { + + /** + * @type {Array<{done: Function, error: Function }>} + */ + this.handlers = []; + + /** + * Register an interceptor + * @param {Function} done + * @param {Function} [error] + * @returns {number} The interceptor Id to be used for ejection + */ + this.use = function(done, error) { + this.handlers.push({ + done, + error: error || (() => {}) + }); + + return this.handlers.length - 1; + }; + + /** + * @param {number} id - A registered interceptor Id + */ + this.eject = function (id) { + if (this.handlers[id]) + this.handlers[id] = null; + }; +} + +export default Interceptor; \ No newline at end of file diff --git a/test/index.test.js b/test/index.test.js index 2682d3d..d3eca3c 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -232,13 +232,63 @@ describe('redaxios', () => { expect(res.data).toEqual({ hello: 'world' }); }); + it('pre-request interceptor', async () => { + // @TODO: adding global interceptors here leaks if tests fail + const preRequestInterceptor = axios.interceptors.request.use((config) => { + config.test = 'testValue'; + return config; + }); + const req = axios.get(jsonExample, { + responseType: 'json' + }); + expect(req).toBeInstanceOf(Promise); + const res = await req; + expect(res).toBeInstanceOf(Object); + expect(res.config.test).toBe('testValue'); + + // eject the interceptor + axios.interceptors.request.eject(preRequestInterceptor); + + const newReq = axios.get(jsonExample, { + responseType: 'json' + }); + expect(newReq).toBeInstanceOf(Promise); + const newRes = await newReq; + expect(newRes).toBeInstanceOf(Object); + expect(newRes.config.test).toBe(undefined); + }); + + it('response interceptor', async () => { + const postResponseInterceptor = axios.interceptors.response.use((response) => { + response.data.hello = `${response.data.hello} from interceptor`; + return response; + }); + const req = axios.get(jsonExample, { + responseType: 'json' + }); + expect(req).toBeInstanceOf(Promise); + const res = await req; + expect(res).toBeInstanceOf(Object); + expect(res.data).toEqual({ hello: 'world from interceptor' }); + + // eject the interceptor + axios.interceptors.response.eject(postResponseInterceptor); + + const newReq = axios.get(jsonExample, { + responseType: 'json' + }); + expect(newReq).toBeInstanceOf(Promise); + const newRes = await newReq; + expect(newRes).toBeInstanceOf(Object); + expect(newRes.data).toEqual({ hello: 'world' }); + }); + describe('options.params & options.paramsSerializer', () => { let oldFetch, fetchMock; beforeEach(() => { oldFetch = window.fetch; fetchMock = window.fetch = jasmine.createSpy('fetch').and.returnValue(Promise.resolve()); }); - afterEach(() => { window.fetch = oldFetch; });