Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,8 @@
"sinon": "^7.2.7",
"sinon-chai": "^3.3.0",
"typescript": "^3.9.5"
},
"dependencies": {
"tiny-set-immediate": "^1.0.2"
}
}
8 changes: 3 additions & 5 deletions taskscheduler.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
63 changes: 10 additions & 53 deletions test/TaskScheduler-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,28 +51,27 @@ describe('TaskScheduler', () => {
});
});

describe('a task scheduler when setImmediate exists', () => {
describe('a task scheduler using an externally-provided setImmediate() implementation', () => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
describe('a task scheduler using an externally-provided setImmediate() implementation', () => {
describe('a task scheduler with default setImmediate implementation', () => {

const backups = {};
let setImmediate = sinon.spy();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just let it default here; or spy on tiny-set-immediate itself


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();
Expand All @@ -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
Expand All @@ -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();
Expand All @@ -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
Expand All @@ -121,48 +120,6 @@ describe('TaskScheduler', () => {
});
});

describe('a task scheduler when setImmediate does not exist', () => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe keep this test, but repurpose for custom implementation? I like that the test verifies the alternating behavior in another way.

describe('a task scheduler with custom setImmediate implementation',

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 = {};

Expand All @@ -176,7 +133,7 @@ describe('TaskScheduler', () => {
});

it('can schedule a task', done => {
const taskScheduler = createTaskScheduler();
const taskScheduler = createTaskScheduler(setImmediate);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest to leave unchanged

taskScheduler(done);
});
});
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
Loading