Skip to content

Commit bf6a067

Browse files
committed
feat: add SYNC priority and sleep
1 parent 53d6237 commit bf6a067

File tree

7 files changed

+129
-49
lines changed

7 files changed

+129
-49
lines changed

.npmrc

Lines changed: 0 additions & 1 deletion
This file was deleted.

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ Queue instances can be created with the `Queue` constructor. The constructor req
6767

6868
The following priorities are exported on the `priorities` object.
6969

70+
- `priorities.SYNC`: Tasks are executed right away synchronously.
7071
- `priorities.CRITICAL`: Tasks are executed ASAP (always before the next repaint in the browser).
7172
- `priorities.HIGH`: Tasks are executed when there is free time and no more pending critical tasks.
7273
- `priorities.LOW`: Tasks are executed when there is free time and no more pending critical or high prio tasks.
@@ -91,14 +92,14 @@ Clears every task from the queue without executing them.
9192

9293
Executes every task in the queue, then clears the queue.
9394

94-
### queue.start()
95-
96-
Starts the - priority based - automatic task execution of the queue. The queue is automatically started after creation.
97-
9895
### queue.stop()
9996

10097
Stops the automatic task execution of the queue.
10198

99+
### queue.start()
100+
101+
Starts the - priority based - automatic task execution of the queue. The queue is automatically started after creation.
102+
102103
### promise = queue.processing()
103104

104105
Returns a promise, which resolves after all of the current tasks in the queue are executed. If the queue is empty, it resolves immediately.

scripts/test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const coverage = require('rollup-plugin-coverage')
66
const alias = require('rollup-plugin-alias')
77
const TestServer = require('karma').Server
88

9-
const bundleName = process.env.BUNDLE_TYPE
9+
const bundleName = process.env.BUNDLE
1010
const bundlePath = bundleName ? `dist/${bundleName}` : 'src/index.js'
1111

1212
const config = {

src/Queue.js

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import { queues, validatePriority } from './queues'
1+
import { queues, priorities } from './queues'
22
import { queueTaskProcessing, runTask } from './scheduling'
33

44
const QUEUE = Symbol('task queue')
5+
const IS_STOPPED = Symbol('is stopped')
6+
const IS_SLEEPING = Symbol('is sleeping')
57

68
export class Queue {
7-
constructor (priority) {
8-
this.priority = validatePriority(priority)
9+
constructor (priority = priorities.SYNC) {
910
this[QUEUE] = new Set()
11+
this.priority = priority
1012
queues[this.priority].push(this[QUEUE])
1113
}
1214

@@ -15,14 +17,18 @@ export class Queue {
1517
}
1618

1719
add (task) {
18-
if (typeof task !== 'function') {
19-
throw new TypeError(
20-
`${task} can not be added to the queue. Only functions can be added.`
21-
)
20+
if (this[IS_SLEEPING]) {
21+
return
22+
}
23+
if (this.priority === priorities.SYNC && !this[IS_STOPPED]) {
24+
task()
25+
} else {
26+
const queue = this[QUEUE]
27+
queue.add(task)
28+
}
29+
if (!this[IS_STOPPED]) {
30+
queueTaskProcessing(this.priority)
2231
}
23-
const queue = this[QUEUE]
24-
queue.add(task)
25-
queueTaskProcessing(this.priority)
2632
}
2733

2834
delete (task) {
@@ -31,11 +37,17 @@ export class Queue {
3137

3238
start () {
3339
const queue = this[QUEUE]
34-
const priorityQueues = queues[this.priority]
35-
if (priorityQueues.indexOf(queue) === -1) {
36-
priorityQueues.push(queue)
40+
if (this.priority === priorities.SYNC) {
41+
this.process()
42+
} else {
43+
const priorityQueues = queues[this.priority]
44+
if (priorityQueues.indexOf(queue) === -1) {
45+
priorityQueues.push(queue)
46+
}
47+
queueTaskProcessing(this.priority)
3748
}
38-
queueTaskProcessing(this.priority)
49+
this[IS_STOPPED] = false
50+
this[IS_SLEEPING] = false
3951
}
4052

4153
stop () {
@@ -45,6 +57,12 @@ export class Queue {
4557
if (index !== -1) {
4658
priorityQueues.splice(index, 1)
4759
}
60+
this[IS_STOPPED] = true
61+
}
62+
63+
sleep () {
64+
this.stop()
65+
this[IS_SLEEPING] = true
4866
}
4967

5068
clear () {

src/queues.js

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
11
export const priorities = {
2-
CRITICAL: 0,
3-
HIGH: 1,
4-
LOW: 2
2+
SYNC: 0,
3+
CRITICAL: 1,
4+
HIGH: 2,
5+
LOW: 3
56
}
6-
const validPriorities = new Set([0, 1, 2])
77

88
export const queues = {
9+
[priorities.SYNC]: [],
910
[priorities.CRITICAL]: [],
1011
[priorities.HIGH]: [],
1112
[priorities.LOW]: []
1213
}
13-
14-
export function validatePriority (priority) {
15-
if (!validPriorities.has(priority)) {
16-
throw new Error(`Invalid queue priority: ${priority}.`)
17-
}
18-
return priority
19-
}

src/scheduling.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export function queueTaskProcessing (priority) {
99
nextTick(runQueuedCriticalTasks)
1010
} else if (priority === priorities.HIGH) {
1111
nextAnimationFrame(runQueuedHighTasks)
12-
} else {
12+
} else if (priority === priorities.LOW) {
1313
nextIdlePeriod(runQueuedLowTasks)
1414
}
1515
}

tests/Queue.test.js

Lines changed: 85 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,6 @@ import { spy, beforeNextFrame } from './utils'
66
chai.use(dirtyChai)
77

88
describe('Queue', () => {
9-
it('should throw on invalid priority argument', () => {
10-
expect(() => new Queue()).to.throw(Error)
11-
expect(() => new Queue(null)).to.throw(Error)
12-
expect(() => new Queue(priorities.CRITICAL)).to.not.throw()
13-
expect(() => new Queue(priorities.HIGH)).to.not.throw()
14-
expect(() => new Queue(priorities.LOW)).to.not.throw()
15-
})
16-
179
it('should auto run the added tasks', async () => {
1810
const queue = new Queue(priorities.CRITICAL)
1911
const taskSpy1 = spy(() => {})
@@ -37,19 +29,22 @@ describe('Queue', () => {
3729
})
3830

3931
describe('add', () => {
40-
it('should throw on non function first argument', () => {
41-
const queue = new Queue(priorities.HIGH)
42-
expect(() => queue.add()).to.throw(TypeError)
43-
expect(() => queue.add({})).to.throw(TypeError)
44-
expect(() => queue.add(null)).to.throw(TypeError)
45-
})
46-
4732
it('should add the task to the queue', () => {
4833
const queue = new Queue(priorities.HIGH)
49-
const task = () => {}
34+
const task = spy(() => {})
5035
expect(queue.has(task)).to.be.false()
5136
queue.add(task)
5237
expect(queue.has(task)).to.be.true()
38+
expect(task.callCount).to.eql(0)
39+
})
40+
41+
it('should run the task if it has a SYNC priority', () => {
42+
const queue = new Queue(priorities.SYNC)
43+
const task = spy(() => {})
44+
queue.add(task)
45+
expect(task.callCount).to.eql(1)
46+
queue.add(task)
47+
expect(task.callCount).to.eql(2)
5348
})
5449

5550
it('should ignore duplicate entries', () => {
@@ -73,6 +68,16 @@ describe('Queue', () => {
7368
queue.delete(task)
7469
expect(queue.has(task)).to.be.false()
7570
})
71+
72+
it('should delete async tasks from the queue', () => {
73+
const queue = new Queue(priorities.SYNC)
74+
const task = () => {}
75+
queue.stop()
76+
queue.add(task)
77+
expect(queue.has(task)).to.be.true()
78+
queue.delete(task)
79+
expect(queue.has(task)).to.be.false()
80+
})
7681
})
7782

7883
describe('size', () => {
@@ -152,10 +157,21 @@ describe('Queue', () => {
152157
expect(queue.size).to.eql(1)
153158
expect(taskSpy.callCount).to.eql(1)
154159
})
160+
161+
it('should queue tasks instead of discarding them with SYNC priority', () => {
162+
const queue = new Queue(priorities.SYNC)
163+
const taskSpy = spy(() => {})
164+
expect(queue.size).to.eql(0)
165+
queue.stop()
166+
queue.add(taskSpy)
167+
queue.add(taskSpy)
168+
expect(queue.size).to.eql(1)
169+
expect(taskSpy.callCount).to.eql(0)
170+
})
155171
})
156172

157173
describe('start', () => {
158-
it('should start the automatic queue processing', async () => {
174+
it('should start the automatic queue processing after a stop', async () => {
159175
const queue = new Queue(priorities.CRITICAL)
160176
const taskSpy = spy(() => {})
161177
queue.add(taskSpy)
@@ -169,6 +185,20 @@ describe('Queue', () => {
169185
expect(taskSpy.callCount).to.eql(1)
170186
})
171187

188+
it('should start the automatic queue processing after a sleep', async () => {
189+
const queue = new Queue(priorities.CRITICAL)
190+
const taskSpy = spy(() => {})
191+
queue.add(taskSpy)
192+
queue.sleep()
193+
await beforeNextFrame()
194+
expect(queue.size).to.eql(1)
195+
expect(taskSpy.callCount).to.eql(0)
196+
queue.start()
197+
await queue.processing()
198+
expect(queue.size).to.eql(0)
199+
expect(taskSpy.callCount).to.eql(1)
200+
})
201+
172202
it('should should have the same effect on multiple calls', async () => {
173203
const queue = new Queue(priorities.CRITICAL)
174204
const taskSpy = spy(() => {})
@@ -184,6 +214,44 @@ describe('Queue', () => {
184214
expect(queue.size).to.eql(0)
185215
expect(taskSpy.callCount).to.eql(1)
186216
})
217+
218+
it('should process tasks with SYNC priority', async () => {
219+
const queue = new Queue(priorities.SYNC)
220+
const taskSpy = spy(() => {})
221+
expect(queue.size).to.eql(0)
222+
queue.stop()
223+
queue.add(taskSpy)
224+
queue.add(taskSpy)
225+
await beforeNextFrame()
226+
expect(queue.size).to.eql(1)
227+
expect(taskSpy.callCount).to.eql(0)
228+
queue.start()
229+
expect(queue.size).to.eql(0)
230+
expect(taskSpy.callCount).to.eql(1)
231+
})
232+
})
233+
234+
describe('sleep', () => {
235+
it('should stop the automatic queue processing', async () => {
236+
const queue = new Queue(priorities.CRITICAL)
237+
const taskSpy = spy(() => {})
238+
expect(queue.size).to.eql(0)
239+
queue.add(taskSpy)
240+
queue.sleep()
241+
await beforeNextFrame()
242+
expect(queue.size).to.eql(1)
243+
expect(taskSpy.callCount).to.eql(0)
244+
})
245+
246+
it('should discard new tasks', async () => {
247+
const queue = new Queue(priorities.CRITICAL)
248+
const taskSpy = spy(() => {})
249+
queue.sleep()
250+
queue.add(taskSpy)
251+
expect(queue.size).to.eql(0)
252+
await beforeNextFrame()
253+
expect(taskSpy.callCount).to.eql(0)
254+
})
187255
})
188256

189257
describe('process', () => {

0 commit comments

Comments
 (0)