From 0673a210960f77ee0b8b99074b93d743286d88e8 Mon Sep 17 00:00:00 2001 From: Diego Fidalgo Date: Sun, 2 Jul 2023 16:10:35 -0300 Subject: [PATCH] feat: partial sync execution to req interceptors --- lib/core/Axios.js | 77 ++++++++++++++++++--------------- test/specs/interceptors.spec.js | 27 +++++++++--- 2 files changed, 64 insertions(+), 40 deletions(-) diff --git a/lib/core/Axios.js b/lib/core/Axios.js index 7a6e6f5d44..2bb2ec9ba1 100644 --- a/lib/core/Axios.js +++ b/lib/core/Axios.js @@ -92,13 +92,21 @@ class Axios { // filter out skipped interceptors const requestInterceptorChain = []; - let synchronousRequestInterceptors = true; + + // We'll keep this to track the position of the last asynchronous request + // interceptor + let requestInterceptorsCount = 0; + let asyncInterceptorsThreshold = 0; this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) { return; } - synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous; + requestInterceptorsCount++; + + if (!interceptor.synchronous) { + asyncInterceptorsThreshold = requestInterceptorsCount; + } requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected); }); @@ -108,53 +116,52 @@ class Axios { responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected); }); - let promise; - let i = 0; - let len; - - if (!synchronousRequestInterceptors) { - const chain = [dispatchRequest.bind(this), undefined]; - chain.unshift.apply(chain, requestInterceptorChain); - chain.push.apply(chain, responseInterceptorChain); - len = chain.length; - - promise = Promise.resolve(config); - - while (i < len) { - promise = promise.then(chain[i++], chain[i++]); - } + // Since we are always unshifting two elements into the request interceptor + // chain, we must decrease 2 times the threshold amount of async interceptors + // to get the synchronous request interceptor chain length. + const syncLen = requestInterceptorChain.length - 2 * asyncInterceptorsThreshold; - return promise; - } + const chain = [dispatchRequest.bind(this), undefined]; - len = requestInterceptorChain.length; + chain.unshift.apply(chain, requestInterceptorChain); + chain.push.apply(chain, responseInterceptorChain); - let newConfig = config; + const totalLen = chain.length; - i = 0; + let i = 0; - while (i < len) { - const onFulfilled = requestInterceptorChain[i++]; - const onRejected = requestInterceptorChain[i++]; + while (i < syncLen) { + const onFulfilled = chain[i++]; + const onRejected = chain[i++]; try { - newConfig = onFulfilled(newConfig); + config = onFulfilled(config); } catch (error) { onRejected.call(this, error); break; } } - try { - promise = dispatchRequest.call(this, newConfig); - } catch (error) { - return Promise.reject(error); - } + let promise; - i = 0; - len = responseInterceptorChain.length; + if (i < requestInterceptorChain.length) { + // There are asynchronous request interceptors before the request dispatcher + // itself is called. + promise = Promise.resolve(config); + } + else { + // All the request interceptors were executed synchronously. Then, the + // next element on the interceptor chain will be the request dispatcher + // itself and, in this case, we must call it synchronously. + try { + i += 2; + promise = dispatchRequest.call(this, config); + } catch (error) { + return Promise.reject(error); + } + } - while (i < len) { - promise = promise.then(responseInterceptorChain[i++], responseInterceptorChain[i++]); + while (i < totalLen) { + promise = promise.then(chain[i++], chain[i++]); } return promise; diff --git a/test/specs/interceptors.spec.js b/test/specs/interceptors.spec.js index d08bcba31a..ec92ac2ffb 100644 --- a/test/specs/interceptors.spec.js +++ b/test/specs/interceptors.spec.js @@ -60,14 +60,33 @@ describe('interceptors', function () { }); }); - it('should execute asynchronously when not all interceptors are explicitly flagged as synchronous', function (done) { + it('should execute synchronous interceptors added after async interceptors synchronously', function (done) { let asyncFlag = false; + axios.interceptors.request.use(function (config) { - config.headers.foo = 'uh oh, async'; + config.headers.test = 'added by async interceptor'; expect(asyncFlag).toBe(true); return config; }); + axios.interceptors.request.use(function (config) { + config.headers.test = 'added by synchronous interceptor'; + expect(asyncFlag).toBe(false); + return config; + }, null, { synchronous: true }); + + axios('/foo'); + asyncFlag = true; + + getAjaxRequest().then(function (request) { + expect(request.requestHeaders.test).toBe('added by async interceptor'); + done(); + }); + }); + + it('should execute synchronous interceptors added before async interceptors asynchronously', function (done) { + let asyncFlag = false; + axios.interceptors.request.use(function (config) { config.headers.test = 'added by synchronous interceptor'; expect(asyncFlag).toBe(true); @@ -75,7 +94,7 @@ describe('interceptors', function () { }, null, { synchronous: true }); axios.interceptors.request.use(function (config) { - config.headers.test = 'added by the async interceptor'; + config.headers.test = 'added by async interceptor'; expect(asyncFlag).toBe(true); return config; }); @@ -84,8 +103,6 @@ describe('interceptors', function () { asyncFlag = true; getAjaxRequest().then(function (request) { - expect(request.requestHeaders.foo).toBe('uh oh, async'); - /* request interceptors have a reversed execution order */ expect(request.requestHeaders.test).toBe('added by synchronous interceptor'); done(); });