From 5ad5fe0dc63fd9489c822bded5de657007ae6011 Mon Sep 17 00:00:00 2001 From: mixelburg <52622705+mixelburg@users.noreply.github.com> Date: Wed, 6 May 2026 20:28:31 +0300 Subject: [PATCH] refactor: clean up composeSignals early-return structure (#10844) Flips the main if-block to an early-return pattern to reduce nesting. Explicitly initializes aborted to false. Adds a guard clause in unsubscribe to avoid duplicate cleanup. Preserves the existing manual event-listener approach because AbortSignal.any() introduces event-loop timing differences that break fetch adapter stream-abort tests. Closes #10815. Co-authored-by: mixelburg Co-authored-by: Jay --- lib/helpers/composeSignals.js | 93 ++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/lib/helpers/composeSignals.js b/lib/helpers/composeSignals.js index 04e3012d..74e99ed4 100644 --- a/lib/helpers/composeSignals.js +++ b/lib/helpers/composeSignals.js @@ -3,54 +3,55 @@ import AxiosError from '../core/AxiosError.js'; import utils from '../utils.js'; const composeSignals = (signals, timeout) => { - const { length } = (signals = signals ? signals.filter(Boolean) : []); + signals = signals ? signals.filter(Boolean) : []; - if (timeout || length) { - let controller = new AbortController(); - - let aborted; - - const onabort = function (reason) { - if (!aborted) { - aborted = true; - unsubscribe(); - const err = reason instanceof Error ? reason : this.reason; - controller.abort( - err instanceof AxiosError - ? err - : new CanceledError(err instanceof Error ? err.message : err) - ); - } - }; - - let timer = - timeout && - setTimeout(() => { - timer = null; - onabort(new AxiosError(`timeout of ${timeout}ms exceeded`, AxiosError.ETIMEDOUT)); - }, timeout); - - const unsubscribe = () => { - if (signals) { - timer && clearTimeout(timer); - timer = null; - signals.forEach((signal) => { - signal.unsubscribe - ? signal.unsubscribe(onabort) - : signal.removeEventListener('abort', onabort); - }); - signals = null; - } - }; - - signals.forEach((signal) => signal.addEventListener('abort', onabort)); - - const { signal } = controller; - - signal.unsubscribe = () => utils.asap(unsubscribe); - - return signal; + if (!timeout && !signals.length) { + return; } + + const controller = new AbortController(); + + let aborted = false; + + const onabort = function (reason) { + if (!aborted) { + aborted = true; + unsubscribe(); + const err = reason instanceof Error ? reason : this.reason; + controller.abort( + err instanceof AxiosError + ? err + : new CanceledError(err instanceof Error ? err.message : err) + ); + } + }; + + let timer = + timeout && + setTimeout(() => { + timer = null; + onabort(new AxiosError(`timeout of ${timeout}ms exceeded`, AxiosError.ETIMEDOUT)); + }, timeout); + + const unsubscribe = () => { + if (!signals) { return; } + timer && clearTimeout(timer); + timer = null; + signals.forEach((signal) => { + signal.unsubscribe + ? signal.unsubscribe(onabort) + : signal.removeEventListener('abort', onabort); + }); + signals = null; + }; + + signals.forEach((signal) => signal.addEventListener('abort', onabort)); + + const { signal } = controller; + + signal.unsubscribe = () => utils.asap(unsubscribe); + + return signal; }; export default composeSignals;