Skip to content

Commit a0f23e7

Browse files
committed
feat: add key support
Allows a finer grained limitation, based on the context and arguments of the call.
1 parent c3231ce commit a0f23e7

File tree

1 file changed

+51
-7
lines changed

1 file changed

+51
-7
lines changed

src/index.js

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@ function Deferred(fn, thisArg, args, resolve, reject) {
66
this.thisArg = thisArg;
77
}
88

9+
function defaultOnEmpty() {
10+
++this.concurrency;
11+
}
12+
913
function next() {
1014
const d = this.pop();
1115
if (d === undefined) {
12-
++this.concurrency;
16+
this.onEmpty();
1317
} else {
1418
try {
1519
d.resolve(d.fn.apply(d.thisArg, d.args));
@@ -20,10 +24,11 @@ function next() {
2024
}
2125

2226
// based on implementation in https://github.com/ForbesLindesay/throat
23-
function Queue(concurrency) {
27+
function Queue(concurrency, onEmpty) {
2428
// not related to the queue implementation but used in this lib
2529
this.concurrency = concurrency;
2630
this.next = next.bind(this);
31+
this.onEmpty = onEmpty !== undefined ? onEmpty : defaultOnEmpty;
2732

2833
this._s1 = []; // stack to push to
2934
this._s2 = []; // stack to pop from
@@ -60,7 +65,7 @@ const { slice } = Array.prototype;
6065
const makeLimiter = (getQueue, termination = defaultTermination) => {
6166
return fn =>
6267
function() {
63-
const queue = getQueue(this);
68+
const queue = getQueue(this, arguments);
6469
const canRun = queue.concurrency > 0;
6570
let argStart = 0;
6671
const { length } = arguments;
@@ -99,10 +104,24 @@ const limitFunction = (concurrency, opts) => {
99104
const queue = new Queue(concurrency);
100105
return makeLimiter(() => queue, opts);
101106
};
107+
const limitFunctionWithKey = keyFunction => (concurrency, opts) => {
108+
const queues = new Map();
109+
return makeLimiter((thisArg, args) => {
110+
const key = keyFunction.apply(thisArg, args);
111+
let queue = queues.get(key);
112+
if (queue === undefined) {
113+
queue = new Queue(concurrency, () => {
114+
queues.delete(key);
115+
});
116+
queues.set(key, queue);
117+
}
118+
return queue;
119+
});
120+
};
102121

103122
// create a method limiter where the concurrency is shared between all
104123
// methods but locally to the instance
105-
export const limitMethod = (concurrency, opts) => {
124+
const limitMethod = (concurrency, opts) => {
106125
const queues = new WeakMap();
107126
return makeLimiter(obj => {
108127
let queue = queues.get(obj);
@@ -113,14 +132,30 @@ export const limitMethod = (concurrency, opts) => {
113132
return queue;
114133
}, opts);
115134
};
135+
const limitMethodWithKey = keyFunction => (concurrency, opts) => {
136+
const queuesByInstance = new WeakMap();
137+
return makeLimiter((thisArg, args) => {
138+
let queues = queuesByInstance.get(thisArg);
139+
if (queues === undefined) {
140+
queues = new Map();
141+
queuesByInstance.set(thisArg, queues);
142+
}
143+
let queue = queuesByInstance.get(thisArg);
144+
if (queue === undefined) {
145+
queue = new Queue(concurrency);
146+
queuesByInstance.set(thisArg, queue);
147+
}
148+
return queue;
149+
});
150+
};
116151

117-
export default (...args) => {
152+
const makeDecorator = (decorateFunction, decorateMethod) => (...args) => {
118153
let method = false;
119154
let wrap;
120155
return (target, key, descriptor) => {
121156
if (key === undefined) {
122157
if (wrap === undefined) {
123-
wrap = limitFunction(...args);
158+
wrap = decorateFunction(...args);
124159
} else if (method) {
125160
throw new Error(
126161
"the same decorator cannot be used between function and method"
@@ -131,7 +166,7 @@ export default (...args) => {
131166

132167
if (wrap === undefined) {
133168
method = true;
134-
wrap = limitMethod(...args);
169+
wrap = decorateMethod(...args);
135170
} else if (!method) {
136171
throw new Error(
137172
"the same decorator cannot be used between function and method"
@@ -150,3 +185,12 @@ export default (...args) => {
150185
return descriptor;
151186
};
152187
};
188+
189+
const decorator = makeDecorator(limitFunction, limitMethod);
190+
decorator.withKey = keyFunction =>
191+
makeDecorator(
192+
limitFunctionWithKey(keyFunction),
193+
limitMethodWithKey(keyFunction)
194+
);
195+
196+
export { decorator as default };

0 commit comments

Comments
 (0)