forked from dequelabs/axe-core
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathqueue.js
More file actions
137 lines (124 loc) · 3.14 KB
/
queue.js
File metadata and controls
137 lines (124 loc) · 3.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import log from '../log';
function noop() {}
function funcGuard(f) {
if (typeof f !== 'function') {
throw new TypeError('Queue methods require functions as arguments');
}
}
/**
* Create an asynchronous "queue", list of functions to be invoked in parallel, but not necessarily returned in order
* @return {Queue} The newly generated "queue"
*/
function queue() {
const tasks = [];
let started = 0;
let remaining = 0; // number of tasks not yet finished
let completeQueue = noop;
let complete = false;
let err;
// By default, wait until the next tick,
// if no catch was set, throw to console.
const defaultFail = e => {
err = e;
setTimeout(() => {
if (err !== undefined && err !== null) {
log('Uncaught error (of queue)', err);
}
}, 1);
};
let failed = defaultFail;
function createResolve(i) {
return r => {
tasks[i] = r;
remaining -= 1;
if (!remaining && completeQueue !== noop) {
complete = true;
completeQueue(tasks);
}
};
}
function abort(msg) {
// reset tasks
completeQueue = noop;
// notify catch
failed(msg);
// return unfinished work
return tasks;
}
function pop() {
const length = tasks.length;
for (; started < length; started++) {
const task = tasks[started];
try {
task.call(null, createResolve(started), abort);
} catch (e) {
abort(e);
}
}
}
const q = {
/**
* Defer a function that may or may not run asynchronously.
*
* First parameter should be the function to execute with subsequent
* parameters being passed as arguments to that function
*/
defer(fn) {
if (typeof fn === 'object' && fn.then && fn.catch) {
const defer = fn;
fn = (resolve, reject) => {
defer.then(resolve).catch(reject);
};
}
funcGuard(fn);
if (err !== undefined) {
return;
} else if (complete) {
throw new Error('Queue already completed');
}
tasks.push(fn);
++remaining;
pop();
return q;
},
/**
* The callback to execute once all "deferred" functions have completed. Will only be invoked once.
* @param {Function} f The callback, receives an array of the return/callbacked
* values of each of the "deferred" functions
*/
then(fn) {
funcGuard(fn);
if (completeQueue !== noop) {
throw new Error('queue `then` already set');
}
if (!err) {
completeQueue = fn;
if (!remaining) {
complete = true;
completeQueue(tasks);
}
}
return q;
},
catch: function (fn) {
funcGuard(fn);
if (failed !== defaultFail) {
throw new Error('queue `catch` already set');
}
if (!err) {
failed = fn;
} else {
fn(err);
err = null;
}
return q;
},
/**
* Abort the "queue" and prevent `then` function from firing
* @param {Function} fn The callback to execute; receives an array of the results which have completed
*/
abort: abort
};
return q;
}
export default queue;