Syntax previewClick to edit
12345
export default async function fetchJson(url, options = {}) {
// TODO: support timeoutMs + AbortSignal, throw on non-OK responses
throw new Error('Not implemented');
}
Syntax previewClick to edit
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
import fetchJson from './fetchJson';
function toAbortError(reason, message = 'Aborted') {
if (reason && reason.name === 'AbortError') return reason;
const err = new Error(reason && reason.message ? reason.message : message);
try { err.name = 'AbortError'; } catch { try { Object.defineProperty(err, 'name', { value: 'AbortError' }); } catch {} }
return err;
}
function makeFetch({ delay = 0, ok = true, status = 200, jsonData = {} } = {}) {
return function fetchMock(_url, init = {}) {
const signal = init.signal;
return new Promise((resolve, reject) => {
let done = false;
const finish = () => {
if (done) return;
done = true;
if (signal) signal.removeEventListener('abort', onAbort);
resolve({
ok,
status,
json: async () => jsonData
});
};
const onAbort = () => {
if (done) return;
done = true;
clearTimeout(timerId);
reject(toAbortError(signal && signal.reason));
};
if (signal) {
if (signal.aborted) return onAbort();
signal.addEventListener('abort', onAbort, { once: true });
}
const timerId = setTimeout(finish, delay);
});
};
}
describe('fetchJson', () => {
test('resolves JSON for ok responses', async () => {
const fetchFn = makeFetch({ jsonData: { ok: true } });
await expect(fetchJson('/ok', { fetchFn, timeoutMs: 100 })).resolves.toEqual({ ok: true });
});
test('throws on non-ok status', async () => {
const fetchFn = makeFetch({ ok: false, status: 404 });
await expect(fetchJson('/missing', { fetchFn })).rejects.toThrow('HTTP 404');
});
test('aborts on timeout', async () => {
const fetchFn = makeFetch({ delay: 50, jsonData: { ok: true } });
await expect(fetchJson('/slow', { fetchFn, timeoutMs: 10 })).rejects.toMatchObject({
name: 'AbortError',
message: 'Timeout'
});
});
test('respects external abort signal', async () => {
const ac = new AbortController();
const fetchFn = makeFetch({ delay: 30, jsonData: { ok: true } });
const p = fetchJson('/abort', { fetchFn, signal: ac.signal });
setTimeout(() => ac.abort(new Error('Cancelled')), 5);
await expect(p).rejects.toMatchObject({ name: 'AbortError' });
});
});
Run tests to see results.