Skip to content

Commit

Permalink
feat: [#1468] Adds support for the static method AbortSignal.any() (#…
Browse files Browse the repository at this point in the history
…1469)

* feat: [#1468] Add AbortSignal.any() static method

* chore: [#1468] Fixes after merge

* chore: [#1468] Fixes after merge

---------

Co-authored-by: David Ortner <david@ortner.se>
  • Loading branch information
ezzatron and capricorn86 committed Aug 28, 2024
1 parent 3aa0a14 commit 3e1191e
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 11 deletions.
41 changes: 40 additions & 1 deletion packages/happy-dom/src/fetch/AbortSignal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export default class AbortSignal extends EventTarget {
* @returns AbortSignal instance.
*/
public static abort(reason?: Error): AbortSignal {
const signal = new this[PropertySymbol.window].AbortSignal();
const signal = new this();
(<Error>signal.reason) =
reason ||
new this[PropertySymbol.window].DOMException(
Expand All @@ -86,4 +86,43 @@ export default class AbortSignal extends EventTarget {
(<boolean>signal.aborted) = true;
return signal;
}

/**
* Takes an iterable of abort signals and returns an AbortSignal that is
* aborted when any of the input iterable abort signals are aborted.
*
* The abort reason will be set to the reason of the first signal that is
* aborted. If any of the given abort signals are already aborted then so will
* be the returned AbortSignal.
*
* @param [signals] Iterable of abort signals.
* @returns AbortSignal instance.
*/
public static any(signals: AbortSignal[]): AbortSignal {
for (const signal of signals) {
if (signal.aborted) {
return this.abort(signal.reason);
}
}

const anySignal = new this();
const handlers = new Map<AbortSignal, () => void>();

const stopListening = (): void => {
for (const signal of signals) {
signal.removeEventListener('abort', handlers.get(signal));
}
};

for (const signal of signals) {
const handler = (): void => {
stopListening();
anySignal[PropertySymbol.abort](signal.reason);
};
handlers.set(signal, handler);
signal.addEventListener('abort', handler);
}

return anySignal;
}
}
6 changes: 1 addition & 5 deletions packages/happy-dom/src/fetch/Fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,7 @@ export default class Fetch {
this.#window.location.protocol === 'https:'
) {
throw new this.#window.DOMException(
`Mixed Content: The page at '${
this.#window.location.href
}' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint '${
this.request.url
}'. This request has been blocked; the content must be served over HTTPS.`,
`Mixed Content: The page at '${this.#window.location.href}' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint '${this.request.url}'. This request has been blocked; the content must be served over HTTPS.`,
DOMExceptionNameEnum.securityError
);
}
Expand Down
6 changes: 1 addition & 5 deletions packages/happy-dom/src/fetch/SyncFetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,7 @@ export default class SyncFetch {
this.#window.location.protocol === 'https:'
) {
throw new this.#window.DOMException(
`Mixed Content: The page at '${
this.#window.location.href
}' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint '${
this.request.url
}'. This request has been blocked; the content must be served over HTTPS.`,
`Mixed Content: The page at '${this.#window.location.href}' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint '${this.request.url}'. This request has been blocked; the content must be served over HTTPS.`,
DOMExceptionNameEnum.securityError
);
}
Expand Down
31 changes: 31 additions & 0 deletions packages/happy-dom/test/fetch/AbortSignal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,37 @@ describe('AbortSignal', () => {
});
});

describe('AbortSignal.any()', () => {
it('Returns a signal that is asynchronously aborted when one of the supplied signals is asynchronously aborted.', () => {
const signal1 = new window.AbortSignal();
const signal2 = new window.AbortSignal();
const signal = window.AbortSignal.any([signal1, signal2]);

expect(signal.aborted).toBe(false);

const reason2 = new Error('abort reason 2');
signal2[PropertySymbol.abort](reason2);
const reason1 = new Error('abort reason 1');
signal1[PropertySymbol.abort](reason1);

expect(signal.aborted).toBe(true);
expect(signal.reason).toBe(reason2);
});

it('Returns a signal that is already aborted when one of the supplied signals is already aborted.', () => {
const signal1 = new window.AbortSignal();
const signal2 = new window.AbortSignal();

const reason2 = new Error('abort reason 2');
signal2[PropertySymbol.abort](reason2);

const signal = window.AbortSignal.any([signal1, signal2]);

expect(signal.aborted).toBe(true);
expect(signal.reason).toBe(reason2);
});
});

describe('AbortSignal[Symbol.toStringTag]', () => {
it('Returns AbortSignal string.', () => {
const description = 'AbortSignal';
Expand Down

0 comments on commit 3e1191e

Please sign in to comment.