Skip to content

Commit b505056

Browse files
committed
Add browser task scheduler.
See #17 (comment)
1 parent abc6a73 commit b505056

9 files changed

+102
-25
lines changed

.change-extension

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/usr/bin/env bash
2+
3+
# The first parameter is the desired extension
4+
extension=$1
5+
shift
6+
7+
# The other parameters are the files to be renamed with the extension
8+
for file in "$@"; do
9+
mv "$file" "${file%.*}.$extension"
10+
done

.eslintrc

+2-1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
no-undef-init: error,
107107
no-undefined: off,
108108
no-unused-vars: [ error, { args: none } ],
109+
"@typescript-eslint/no-unused-vars": [ error, { args: none } ],
109110
no-use-before-define: [ error, { functions: false, classes: false } ],
110111

111112
// Node.js
@@ -229,7 +230,7 @@
229230
no-confusing-arrow: off,
230231
no-const-assign: error,
231232
no-dupe-class-members: error,
232-
no-duplicate-imports: error,
233+
no-duplicate-imports: off,
233234
no-new-symbol: error,
234235
no-restricted-imports: error,
235236
no-this-before-super: error,

.gitignore

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ coverage
33
docs
44
node_modules
55
package
6-
*.log
7-
*.tgz
8-
*.d.ts
6+
*.bak
97
*.cjs
8+
*.d.ts
9+
*.log
1010
*.mjs
11+
*.tgz

asynciterator.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
*/
55

66
import { EventEmitter } from 'events';
7-
import queueMicrotask from 'queue-microtask';
7+
import createTaskScheduler from './taskscheduler';
8+
import type { Task, TaskScheduler } from './taskscheduler';
89

9-
let taskScheduler: TaskScheduler = queueMicrotask;
10+
let taskScheduler: TaskScheduler = createTaskScheduler();
1011

1112
/** Schedules the given ask for asynchronous execution. */
12-
export function scheduleTask(task: () => void): void {
13+
export function scheduleTask(task: Task): void {
1314
taskScheduler(task);
1415
}
1516

@@ -1875,5 +1876,3 @@ type SourceExpression<T> =
18751876

18761877
type InternalSource<T> =
18771878
AsyncIterator<T> & { _destination: AsyncIterator<any> };
1878-
1879-
type TaskScheduler = (task: () => void) => void;

package-lock.json

-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+9-10
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,17 @@
1313
"module": "./asynciterator.mjs",
1414
"sideEffects": false,
1515
"files": [
16-
"asynciterator.mjs",
17-
"asynciterator.cjs",
18-
"asynciterator.d.ts"
16+
"*.mjs",
17+
"*.cjs",
18+
"*.d.ts"
1919
],
2020
"scripts": {
21-
"build": "npm run build:module && npm run build:commonjs && npm run build:types",
22-
"build:module": "tsc --module es2015 && mv asynciterator.js asynciterator.mjs",
23-
"build:commonjs": "tsc --module commonjs && mv asynciterator.js asynciterator.cjs",
24-
"build:types": "tsc -d && rm asynciterator.js",
21+
"build": "npm run build:clean && npm run build:module && npm run build:commonjs && npm run build:types",
22+
"build:clean": "rm -f *.js *.mjs *.cjs *.d.ts",
23+
"build:module": "tsc --module es2015 && ./.change-extension mjs *.js && npm run build:fix-import",
24+
"build:commonjs": "tsc --module commonjs && ./.change-extension cjs *.js",
25+
"build:types": "tsc -d && rm *.js",
26+
"build:fix-import": "sed -i'.bak' -e 's/taskscheduler/taskscheduler.mjs/' asynciterator.mjs",
2527
"prepare": "npm run build",
2628
"test": "npm run build:module && npm run test:microtask",
2729
"test:microtask": "npm run mocha",
@@ -33,9 +35,6 @@
3335
},
3436
"license": "MIT",
3537
"repository": "RubenVerborgh/AsyncIterator",
36-
"dependencies": {
37-
"queue-microtask": "^1.1.2"
38-
},
3938
"devDependencies": {
4039
"@babel/cli": "^7.10.1",
4140
"@babel/core": "^7.10.2",

queue-microtask.d.ts

-1
This file was deleted.

taskscheduler.ts

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const resolved = Promise.resolve(undefined);
2+
3+
// Returns a function that asynchronously schedules a task
4+
export default function createTaskScheduler() : TaskScheduler {
5+
// Use or create a microtask scheduler
6+
const scheduleMicrotask = typeof queueMicrotask === 'function' ?
7+
queueMicrotask : (task: Task) => resolved.then(task);
8+
9+
// If not in the browser, always use the microtask scheduler
10+
if (typeof window === 'undefined')
11+
return scheduleMicrotask;
12+
13+
// In the browser, alternate with setTimeout to avoid freezing
14+
let i = 0;
15+
return (task: Task) => {
16+
if (++i < 100)
17+
scheduleMicrotask(task);
18+
else
19+
setTimeout(task, i = 0);
20+
}
21+
}
22+
23+
export type Task = () => void;
24+
export type TaskScheduler = (task: Task) => void;

test/TaskScheduler-test.js

+49
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import {
44
setTaskScheduler,
55
} from '../asynciterator.mjs';
66

7+
import createTaskScheduler from '../taskscheduler.mjs';
8+
79
describe('TaskScheduler', () => {
810
describe('scheduleTask', () => {
911
it('is a function', () => {
@@ -48,4 +50,51 @@ describe('TaskScheduler', () => {
4850
setTaskScheduler(scheduler);
4951
});
5052
});
53+
54+
describe('a task scheduler for the browser', () => {
55+
const backups = {};
56+
57+
before(() => {
58+
backups.setTimeout = global.setTimeout;
59+
backups.queueMicrotask = global.queueMicrotask;
60+
global.window = {};
61+
global.setTimeout = sinon.spy();
62+
global.queueMicrotask = sinon.spy();
63+
});
64+
65+
after(() => {
66+
global.setTimeout = backups.setTimeout;
67+
global.queueMicrotask = backups.queueMicrotask;
68+
delete global.window;
69+
});
70+
71+
it('alternates between setTimeout and queueMicrotask', () => {
72+
const taskScheduler = createTaskScheduler();
73+
const task = sinon.spy();
74+
for (let i = 0; i < 100; i++)
75+
taskScheduler(task);
76+
expect(global.setTimeout).to.have.callCount(1);
77+
expect(global.queueMicrotask).to.have.callCount(99);
78+
expect(global.setTimeout).to.have.been.calledWith(task, 0);
79+
expect(global.queueMicrotask).to.have.been.calledWith(task);
80+
});
81+
});
82+
83+
describe('a task scheduler when queueMicrotask is unavailable', () => {
84+
const backups = {};
85+
86+
before(() => {
87+
backups.queueMicrotask = global.queueMicrotask;
88+
delete global.queueMicrotask;
89+
});
90+
91+
after(() => {
92+
global.queueMicrotask = backups.queueMicrotask;
93+
});
94+
95+
it('can schedule a task', done => {
96+
const taskScheduler = createTaskScheduler();
97+
taskScheduler(done);
98+
});
99+
});
51100
});

0 commit comments

Comments
 (0)