Syntax previewClick to edit
12345
export default function takeLatest(fn) {
// TODO: return a wrapped function that aborts previous calls and resolves only the latest
throw new Error('Not implemented');
}
Syntax previewClick to edit
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
import takeLatest from './takeLatest';
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 delay(ms, value, signal) {
return new Promise((resolve, reject) => {
const id = setTimeout(() => resolve(value), ms);
if (signal) {
if (signal.aborted) {
clearTimeout(id);
return reject(toAbortError(signal.reason));
}
signal.addEventListener(
'abort',
() => {
clearTimeout(id);
reject(toAbortError(signal.reason));
},
{ once: true }
);
}
});
}
describe('takeLatest', () => {
test('resolves only the latest call', async () => {
const latest = takeLatest((signal, value, ms) => delay(ms, value, signal));
const p1 = latest('first', 30);
const p2 = latest('second', 5);
await expect(p1).rejects.toMatchObject({ name: 'AbortError' });
await expect(p2).resolves.toBe('second');
});
test('propagates errors from the latest call', async () => {
const latest = takeLatest(async () => {
throw new Error('boom');
});
await expect(latest()).rejects.toThrow('boom');
});
test('aborts the previous controller immediately', async () => {
const latest = takeLatest((signal) => delay(20, 'ok', signal));
const p1 = latest();
await new Promise((r) => setTimeout(r, 1));
const p2 = latest();
await expect(p1).rejects.toMatchObject({ name: 'AbortError' });
await expect(p2).resolves.toBe('ok');
});
});
Run tests to see results.