mirror of
https://github.com/bitwarden/browser
synced 2025-12-18 09:13:33 +00:00
[PM-15001] Replace throttle decorator (#15015)
* Add comments to AuditService Abstraction * Replace throttle usage with rxjs mergeMap with concurrent limit * Add test cases for audit service * Remove throttle
This commit is contained in:
@@ -1,97 +0,0 @@
|
||||
import { throttle } from "./throttle";
|
||||
|
||||
describe("throttle decorator", () => {
|
||||
it("should call the function once at a time", async () => {
|
||||
const foo = new Foo();
|
||||
const promises = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
promises.push(foo.bar(1));
|
||||
}
|
||||
await Promise.all(promises);
|
||||
|
||||
expect(foo.calls).toBe(10);
|
||||
});
|
||||
|
||||
it("should call the function once at a time for each object", async () => {
|
||||
const foo = new Foo();
|
||||
const foo2 = new Foo();
|
||||
const promises = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
promises.push(foo.bar(1));
|
||||
promises.push(foo2.bar(1));
|
||||
}
|
||||
await Promise.all(promises);
|
||||
|
||||
expect(foo.calls).toBe(10);
|
||||
expect(foo2.calls).toBe(10);
|
||||
});
|
||||
|
||||
it("should call the function limit at a time", async () => {
|
||||
const foo = new Foo();
|
||||
const promises = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
promises.push(foo.baz(1));
|
||||
}
|
||||
await Promise.all(promises);
|
||||
|
||||
expect(foo.calls).toBe(10);
|
||||
});
|
||||
|
||||
it("should call the function limit at a time for each object", async () => {
|
||||
const foo = new Foo();
|
||||
const foo2 = new Foo();
|
||||
const promises = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
promises.push(foo.baz(1));
|
||||
promises.push(foo2.baz(1));
|
||||
}
|
||||
await Promise.all(promises);
|
||||
|
||||
expect(foo.calls).toBe(10);
|
||||
expect(foo2.calls).toBe(10);
|
||||
});
|
||||
});
|
||||
|
||||
class Foo {
|
||||
calls = 0;
|
||||
inflight = 0;
|
||||
|
||||
@throttle(1, () => "bar")
|
||||
bar(a: number) {
|
||||
this.calls++;
|
||||
this.inflight++;
|
||||
return new Promise((res) => {
|
||||
setTimeout(() => {
|
||||
expect(this.inflight).toBe(1);
|
||||
this.inflight--;
|
||||
res(a * 2);
|
||||
}, Math.random() * 10);
|
||||
});
|
||||
}
|
||||
|
||||
@throttle(5, () => "baz")
|
||||
baz(a: number) {
|
||||
this.calls++;
|
||||
this.inflight++;
|
||||
return new Promise((res) => {
|
||||
setTimeout(() => {
|
||||
expect(this.inflight).toBeLessThanOrEqual(5);
|
||||
this.inflight--;
|
||||
res(a * 3);
|
||||
}, Math.random() * 10);
|
||||
});
|
||||
}
|
||||
|
||||
@throttle(1, () => "qux")
|
||||
qux(a: number) {
|
||||
this.calls++;
|
||||
this.inflight++;
|
||||
return new Promise((res) => {
|
||||
setTimeout(() => {
|
||||
expect(this.inflight).toBe(1);
|
||||
this.inflight--;
|
||||
res(a * 3);
|
||||
}, Math.random() * 10);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
/**
|
||||
* Use as a Decorator on async functions, it will limit how many times the function can be
|
||||
* in-flight at a time.
|
||||
*
|
||||
* Calls beyond the limit will be queued, and run when one of the active calls finishes
|
||||
*/
|
||||
export function throttle(limit: number, throttleKey: (args: any[]) => string) {
|
||||
return <T>(
|
||||
target: any,
|
||||
propertyKey: string | symbol,
|
||||
descriptor: TypedPropertyDescriptor<(...args: any[]) => Promise<T>>,
|
||||
) => {
|
||||
const originalMethod: () => Promise<T> = descriptor.value;
|
||||
const allThrottles = new Map<any, Map<string, (() => void)[]>>();
|
||||
|
||||
const getThrottles = (obj: any) => {
|
||||
let throttles = allThrottles.get(obj);
|
||||
if (throttles != null) {
|
||||
return throttles;
|
||||
}
|
||||
throttles = new Map<string, (() => void)[]>();
|
||||
allThrottles.set(obj, throttles);
|
||||
return throttles;
|
||||
};
|
||||
|
||||
return {
|
||||
value: function (...args: any[]) {
|
||||
const throttles = getThrottles(this);
|
||||
const argsThrottleKey = throttleKey(args);
|
||||
let queue = throttles.get(argsThrottleKey);
|
||||
if (queue == null) {
|
||||
queue = [];
|
||||
throttles.set(argsThrottleKey, queue);
|
||||
}
|
||||
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const exec = () => {
|
||||
const onFinally = () => {
|
||||
queue.splice(queue.indexOf(exec), 1);
|
||||
if (queue.length >= limit) {
|
||||
queue[limit - 1]();
|
||||
} else if (queue.length === 0) {
|
||||
throttles.delete(argsThrottleKey);
|
||||
if (throttles.size === 0) {
|
||||
allThrottles.delete(this);
|
||||
}
|
||||
}
|
||||
};
|
||||
originalMethod
|
||||
.apply(this, args)
|
||||
.then((val: any) => {
|
||||
onFinally();
|
||||
return val;
|
||||
})
|
||||
.catch((err: any) => {
|
||||
onFinally();
|
||||
throw err;
|
||||
})
|
||||
.then(resolve, reject);
|
||||
};
|
||||
queue.push(exec);
|
||||
if (queue.length <= limit) {
|
||||
exec();
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user