initial commit
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
export function makeid(length) {
|
||||
let result = '';
|
||||
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let charactersLength = characters.length;
|
||||
for ( let i = 0; i < length; i++ ) {
|
||||
result += characters.charAt(Math.floor(Math.random() *
|
||||
charactersLength));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
export type Lazy<T> = () => Promise<T>;
|
||||
@@ -0,0 +1,23 @@
|
||||
export class ListenerCleaner {
|
||||
cleaners: (() => void)[] = [];
|
||||
|
||||
add(cleanerCallback: () => void) {
|
||||
this.cleaners.push(cleanerCallback);
|
||||
}
|
||||
|
||||
cleaner(): () => void {
|
||||
return () => {
|
||||
this.cleanUp();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call all the cleaner callbacks and reset this cleaner to be reused.
|
||||
*/
|
||||
cleanUp() {
|
||||
for (const cleaner of this.cleaners) {
|
||||
cleaner();
|
||||
}
|
||||
this.cleaners = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import {PromiseQueue} from "./PromiseQueue";
|
||||
|
||||
export class MultiQueue<T> {
|
||||
private readonly queues:PromiseQueue<T>[];
|
||||
|
||||
/**
|
||||
*
|
||||
* @param concurrentTask
|
||||
*/
|
||||
constructor(
|
||||
concurrentTask:number=1,
|
||||
) {
|
||||
if (concurrentTask<1){
|
||||
throw new Error("concurrentTask must be >=1");
|
||||
}
|
||||
this.queues = [];
|
||||
for(let i=0;i<concurrentTask;i++){
|
||||
this.queues.push(new PromiseQueue<T>());
|
||||
}
|
||||
}
|
||||
|
||||
public getQueueSize(): number {
|
||||
let ret = 0;
|
||||
for(let queue of this.queues){
|
||||
ret+=queue.getQueueSize();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public async awaitQueueEmpty(){
|
||||
for(let queue of this.queues){
|
||||
await queue.awaitQueueEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a task to the queue. The queue with the smallest task size is used
|
||||
* @param task
|
||||
* @param id
|
||||
**/
|
||||
public add(task: () => Promise<T>, id: string = null): { id: string, promise: Promise<T> } {
|
||||
let queue = this.queues[0];
|
||||
for(let i=1;i<this.queues.length;i++){
|
||||
if(this.queues[i].getQueueSize()<queue.getQueueSize()){
|
||||
queue = this.queues[i];
|
||||
}
|
||||
}
|
||||
return queue.add(task,id);
|
||||
}
|
||||
|
||||
public cancel(id: string) {
|
||||
for(let queue of this.queues){
|
||||
queue.cancel(id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The last task in progress is not canceled
|
||||
*/
|
||||
public async cancelAll(){
|
||||
for(let queue of this.queues){
|
||||
await queue.cancelAll();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import {makeid} from "./Id";
|
||||
|
||||
export class PromiseQueue<T> {
|
||||
private nextPromise: Promise<T> = Promise.resolve(null);
|
||||
private canceled = {};
|
||||
private queueSize = 0;
|
||||
private queueID = 0;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param onlyLast if true all task are cancel and only the last one is keep alive (a task started still finishes)
|
||||
*/
|
||||
constructor(
|
||||
private onlyLast: boolean = false
|
||||
) {
|
||||
this.cancelAll();
|
||||
}
|
||||
|
||||
getQueueSize(): number {
|
||||
return this.queueSize;
|
||||
}
|
||||
|
||||
async awaitQueueEmpty(){
|
||||
await this.nextPromise;//await the current promise
|
||||
while (this.queueSize!=0){//await all the fowllofing promise
|
||||
await this.nextPromise;
|
||||
}
|
||||
}
|
||||
|
||||
add(task: () => Promise<T>, id: string = null): { id: string, promise: Promise<T> } {
|
||||
if (!id) {
|
||||
id = "promise-" + makeid(8);
|
||||
}
|
||||
let lastPromise = this.nextPromise;
|
||||
this.queueSize++;
|
||||
let currentID = this.queueID;
|
||||
let currentPromise=null;
|
||||
currentPromise = (async () => {
|
||||
this.queueSize--;
|
||||
try {
|
||||
await lastPromise;
|
||||
}catch (e) {
|
||||
/* ignore error of previous promise*/
|
||||
}
|
||||
if(currentID!==this.queueID){
|
||||
throw new Error("canceled caused by new queue");
|
||||
}
|
||||
if (this.canceled[id]) {
|
||||
delete this.canceled[id];
|
||||
throw new Error("canceled by user");
|
||||
}
|
||||
if(this.onlyLast && currentPromise!=this.nextPromise){
|
||||
throw new Error("canceled caused by not last");
|
||||
}
|
||||
//execute task and return result
|
||||
return task();
|
||||
})();
|
||||
currentPromise.catch(e=>{
|
||||
/* we catch the exception this way to avoid an uncaught exception in the console*/
|
||||
});
|
||||
this.nextPromise = currentPromise
|
||||
return {id, promise: this.nextPromise};
|
||||
}
|
||||
|
||||
cancel(id: string) {
|
||||
this.canceled[id] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The last task in progress is not canceled
|
||||
*/
|
||||
async cancelAll(){
|
||||
this.queueID++;//create a new queue will cancel all previous task except the one in progress
|
||||
await this.awaitQueueEmpty();
|
||||
this.nextPromise = Promise.resolve(null);
|
||||
this.canceled = {};
|
||||
this.queueSize = 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from "./ListenerCleaner";
|
||||
export * from "./PromiseQueue";
|
||||
export * from "./MultiQueue";
|
||||
export * from "./Id";
|
||||
export * from "./Lazy";
|
||||
Reference in New Issue
Block a user