diff --git a/package-lock.json b/package-lock.json index 5755a36..6106ca8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,9 @@ "name": "asynciterator", "version": "3.9.0", "license": "MIT", + "dependencies": { + "tiny-set-immediate": "^1.0.2" + }, "devDependencies": { "@babel/cli": "^7.10.1", "@babel/core": "^7.10.2", @@ -3926,6 +3929,12 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/tiny-set-immediate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tiny-set-immediate/-/tiny-set-immediate-1.0.2.tgz", + "integrity": "sha512-EVbaM4zXFWS4CIqVoPzY7XIioQ5LU1p49AHizwPO1KyFyp/gxy5SA8mDmfDVl/2WLQiHgUL+esO6Ig+KhpUxUw==", + "license": "MIT" + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -7117,6 +7126,11 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "tiny-set-immediate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tiny-set-immediate/-/tiny-set-immediate-1.0.2.tgz", + "integrity": "sha512-EVbaM4zXFWS4CIqVoPzY7XIioQ5LU1p49AHizwPO1KyFyp/gxy5SA8mDmfDVl/2WLQiHgUL+esO6Ig+KhpUxUw==" + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", diff --git a/package.json b/package.json index 9368a95..df5ce09 100644 --- a/package.json +++ b/package.json @@ -64,5 +64,8 @@ "sinon": "^7.2.7", "sinon-chai": "^3.3.0", "typescript": "^3.9.5" + }, + "dependencies": { + "tiny-set-immediate": "^1.0.2" } } diff --git a/taskscheduler.ts b/taskscheduler.ts index 87a8333..cc80683 100644 --- a/taskscheduler.ts +++ b/taskscheduler.ts @@ -1,15 +1,13 @@ +import { setImmediate } from 'tiny-set-immediate'; + const resolved = Promise.resolve(undefined); // Returns a function that asynchronously schedules a task -export function createTaskScheduler() : TaskScheduler { +export function createTaskScheduler(scheduleMacrotask: TaskScheduler = setImmediate) : TaskScheduler { // Use or create a microtask scheduler const scheduleMicrotask = typeof queueMicrotask === 'function' ? queueMicrotask : (task: Task) => resolved.then(task); - // Use or create a macrotask scheduler - const scheduleMacrotask = typeof setImmediate === 'function' ? - setImmediate : (task: Task) => setTimeout(task, 0); - // Interrupt with a macrotask every once in a while to avoid freezing let i = 0; let queue: Task[] | null = null; diff --git a/test/TaskScheduler-test.js b/test/TaskScheduler-test.js index b4668ec..66ad6e1 100644 --- a/test/TaskScheduler-test.js +++ b/test/TaskScheduler-test.js @@ -51,28 +51,27 @@ describe('TaskScheduler', () => { }); }); - describe('a task scheduler when setImmediate exists', () => { + describe('a task scheduler using an externally-provided setImmediate() implementation', () => { const backups = {}; + let setImmediate = sinon.spy(); beforeEach(() => { backups.setTimeout = global.setTimeout; backups.queueMicrotask = global.queueMicrotask; - backups.setImmediate = global.setImmediate; - global.setImmediate = sinon.spy(); global.setTimeout = sinon.spy(); global.queueMicrotask = sinon.spy(); + setImmediate = sinon.spy(); }); after(() => { global.setTimeout = backups.setTimeout; global.queueMicrotask = backups.queueMicrotask; - global.setImmediate = backups.setImmediate; delete global.window; }); it('alternates between setImmediate and queueMicrotask', () => { // Perform 100 calls - const taskScheduler = createTaskScheduler(); + const taskScheduler = createTaskScheduler(setImmediate); const tasks = []; for (let i = 0; i < 100; i++) { tasks[i] = sinon.spy(); @@ -83,11 +82,11 @@ describe('TaskScheduler', () => { expect(global.setTimeout).to.have.callCount(0); // 99 calls should have gone to queueMicrotask, 1 to setImmediate - expect(global.setImmediate).to.have.callCount(1); + expect(setImmediate).to.have.callCount(1); expect(global.queueMicrotask).to.have.callCount(99); // When the setImmediate callback is invoked, the final call should to through - global.setImmediate.getCall(0).callback(); + setImmediate.getCall(0).callback(); expect(global.queueMicrotask).to.have.callCount(100); // Verify all microtasks were scheduled @@ -97,7 +96,7 @@ describe('TaskScheduler', () => { it('waits for setImmediate to finish before calling queueMicrotask', () => { // Perform 150 calls - const taskScheduler = createTaskScheduler(); + const taskScheduler = createTaskScheduler(setImmediate); const tasks = []; for (let i = 0; i < 150; i++) { tasks[i] = sinon.spy(); @@ -108,11 +107,11 @@ describe('TaskScheduler', () => { expect(global.setTimeout).to.have.callCount(0); // 99 calls should have gone to queueMicrotask, 1 to setImmediate - expect(global.setImmediate).to.have.callCount(1); + expect(setImmediate).to.have.callCount(1); expect(global.queueMicrotask).to.have.callCount(99); // When the setImmediate callback is invoked, the final call should to through - global.setImmediate.getCall(0).callback(); + setImmediate.getCall(0).callback(); expect(global.queueMicrotask).to.have.callCount(150); // Verify all microtasks were scheduled @@ -121,48 +120,6 @@ describe('TaskScheduler', () => { }); }); - describe('a task scheduler when setImmediate does not exist', () => { - const backups = {}; - - before(() => { - backups.setTimeout = global.setTimeout; - backups.queueMicrotask = global.queueMicrotask; - backups.setImmediate = global.setImmediate; - global.setImmediate = undefined; - global.setTimeout = sinon.spy(); - global.queueMicrotask = sinon.spy(); - }); - - after(() => { - global.setTimeout = backups.setTimeout; - global.queueMicrotask = backups.queueMicrotask; - global.setImmediate = backups.setImmediate; - delete global.window; - }); - - it('alternates between setTimeout and queueMicrotask', () => { - // Perform 100 calls - const taskScheduler = createTaskScheduler(); - const tasks = []; - for (let i = 0; i < 100; i++) { - tasks[i] = sinon.spy(); - taskScheduler(tasks[i]); - } - - // 99 calls should have gone to queueMicrotask, 1 to setTimeout - expect(global.setTimeout).to.have.callCount(1); - expect(global.queueMicrotask).to.have.callCount(99); - - // When the setTimeout callback is invoked, the final call should to through - global.setTimeout.getCall(0).args[0](); - expect(global.queueMicrotask).to.have.callCount(100); - - // Verify all microtasks were scheduled - for (let i = 0; i < 100; i++) - expect(global.queueMicrotask).to.have.been.calledWith(tasks[i]); - }); - }); - describe('a task scheduler when queueMicrotask is unavailable', () => { const backups = {}; @@ -176,7 +133,7 @@ describe('TaskScheduler', () => { }); it('can schedule a task', done => { - const taskScheduler = createTaskScheduler(); + const taskScheduler = createTaskScheduler(setImmediate); taskScheduler(done); }); }); diff --git a/tsconfig.json b/tsconfig.json index b33ec79..76b4414 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -39,7 +39,7 @@ "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ /* Module Resolution Options */ - // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */