abort-controller-x
Version:
Abortable async function helpers
197 lines (148 loc) • 4.9 kB
text/typescript
import defer from 'defer-promise';
import {AbortError} from './AbortError';
import {race} from './race';
import {nextTick} from './utils/nextTick';
test('external abort', async () => {
const abortController = new AbortController();
const signal = abortController.signal;
signal.addEventListener = jest.fn(signal.addEventListener);
signal.removeEventListener = jest.fn(signal.removeEventListener);
const deferred1 = defer<string>();
const deferred2 = defer<number>();
let result: PromiseSettledResult<string | number> | undefined;
let innerSignal: AbortSignal;
race(signal, signal => {
innerSignal = signal;
return [deferred1.promise, deferred2.promise];
}).then(
value => {
result = {status: 'fulfilled', value};
},
reason => {
result = {status: 'rejected', reason};
},
);
abortController.abort();
expect(innerSignal!.aborted).toBe(true);
await nextTick();
expect(result).toBeUndefined();
deferred1.reject(new AbortError());
await nextTick();
expect(result).toBeUndefined();
deferred2.reject(new AbortError());
await nextTick();
expect(result).toMatchObject({
status: 'rejected',
reason: {name: 'AbortError'},
});
expect(signal.addEventListener).toHaveBeenCalledTimes(1);
expect(signal.removeEventListener).toHaveBeenCalledTimes(1);
});
test('fulfill', async () => {
const abortController = new AbortController();
const signal = abortController.signal;
signal.addEventListener = jest.fn(signal.addEventListener);
signal.removeEventListener = jest.fn(signal.removeEventListener);
const deferred1 = defer<string>();
const deferred2 = defer<number>();
let result: PromiseSettledResult<string | number> | undefined;
let innerSignal: AbortSignal;
race(signal, signal => {
innerSignal = signal;
return [deferred1.promise, deferred2.promise];
}).then(
value => {
result = {status: 'fulfilled', value};
},
reason => {
result = {status: 'rejected', reason};
},
);
await nextTick();
expect(result).toBeUndefined();
expect(innerSignal!.aborted).toBe(false);
deferred1.resolve('test');
await nextTick();
expect(result).toBeUndefined();
expect(innerSignal!.aborted).toBe(true);
deferred2.reject(new AbortError());
await nextTick();
expect(result).toMatchObject({
status: 'fulfilled',
value: 'test',
});
expect(signal.addEventListener).toHaveBeenCalledTimes(1);
expect(signal.removeEventListener).toHaveBeenCalledTimes(1);
});
test('reject', async () => {
const abortController = new AbortController();
const signal = abortController.signal;
signal.addEventListener = jest.fn(signal.addEventListener);
signal.removeEventListener = jest.fn(signal.removeEventListener);
const deferred1 = defer<string>();
const deferred2 = defer<number>();
let result: PromiseSettledResult<string | number> | undefined;
let innerSignal: AbortSignal;
race(signal, signal => {
innerSignal = signal;
return [deferred1.promise, deferred2.promise];
}).then(
value => {
result = {status: 'fulfilled', value};
},
reason => {
result = {status: 'rejected', reason};
},
);
await nextTick();
expect(result).toBeUndefined();
expect(innerSignal!.aborted).toBe(false);
deferred1.reject('test');
await nextTick();
expect(result).toBeUndefined();
expect(innerSignal!.aborted).toBe(true);
deferred2.reject(new AbortError());
await nextTick();
expect(result).toMatchObject({
status: 'rejected',
reason: 'test',
});
expect(signal.addEventListener).toHaveBeenCalledTimes(1);
expect(signal.removeEventListener).toHaveBeenCalledTimes(1);
});
test('reject during cleanup', async () => {
const abortController = new AbortController();
const signal = abortController.signal;
signal.addEventListener = jest.fn(signal.addEventListener);
signal.removeEventListener = jest.fn(signal.removeEventListener);
const deferred1 = defer<string>();
const deferred2 = defer<number>();
let result: PromiseSettledResult<string | number> | undefined;
let innerSignal: AbortSignal;
race(signal, signal => {
innerSignal = signal;
return [deferred1.promise, deferred2.promise];
}).then(
value => {
result = {status: 'fulfilled', value};
},
reason => {
result = {status: 'rejected', reason};
},
);
abortController.abort();
expect(innerSignal!.aborted).toBe(true);
await nextTick();
expect(result).toBeUndefined();
deferred1.reject(new AbortError());
await nextTick();
expect(result).toBeUndefined();
deferred2.reject(new Error('test'));
await nextTick();
expect(result).toMatchObject({
status: 'rejected',
reason: {message: 'test'},
});
expect(signal.addEventListener).toHaveBeenCalledTimes(1);
expect(signal.removeEventListener).toHaveBeenCalledTimes(1);
});