diff --git a/src/index.mjs b/src/index.mjs index 96066f6..f91884c 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -34,6 +34,14 @@ export default function(url, options) { resolve(response()); }; + if (options.signal) { + options.signal.addEventListener('abort', request.abort.bind(request)); + } + + request.onabort = () => { + reject(new DOMException('The user aborted a request.')); + }; + request.onerror = reject; request.withCredentials = options.credentials=='include'; diff --git a/test/index.js b/test/index.js index 3a9e598..623c579 100644 --- a/test/index.js +++ b/test/index.js @@ -1,6 +1,26 @@ +import { EventEmitter } from 'events'; import fetch from '../src/index.mjs'; import fetchDist from '..'; +// node does not have abort and signal classes +class AbortSignal extends EventEmitter { + constructor() { + super(); + this.addEventListener = jest.fn((type, callback) => { + this.on(type, callback); + }); + } +} + +class AbortController { + constructor() { + this.signal = new AbortSignal(); + } + abort() { + this.signal.emit('abort'); + } +} + describe('unfetch', () => { it('should be a function', () => { expect(fetch).toEqual(expect.any(Function)); @@ -80,5 +100,43 @@ describe('unfetch', () => { return p; }); + + it('respects abort signal', () => { + let xhrs = []; + global.XMLHttpRequest = jest.fn(() => { + let xhr = { + setRequestHeader: jest.fn(), + getAllResponseHeaders: jest.fn().mockReturnValue(''), + open: jest.fn(), + send: jest.fn(), + status: 0, + statusText: '', + responseText: '', + responseURL: '', + onabort: () => {}, + abort: jest.fn(() => xhr.onabort()) + }; + xhrs.push(xhr); + return xhr; + }); + + const controller = new AbortController(); + const p1 = fetch('/foo', { signal: controller.signal }); + const p2 = fetch('/bar', { signal: controller.signal }); + + expect(controller.signal.addEventListener).toHaveBeenCalledTimes(2); + expect(controller.signal.addEventListener).toHaveBeenNthCalledWith(1, 'abort', expect.any(Function)); + expect(controller.signal.addEventListener).toHaveBeenNthCalledWith(2, 'abort', expect.any(Function)); + + controller.abort(); + + expect(p1).rejects.toThrow(/abort/i); + expect(p2).rejects.toThrow(/abort/i); + + expect(xhrs[0].abort).toHaveBeenCalledTimes(1); + expect(xhrs[1].abort).toHaveBeenCalledTimes(1); + + return Promise.all([p1.catch(() => {}), p2.catch(() => {})]); + }); }); });