Last Commit: 2024-01-06 17:50:27
views:
Utility Functions
Wait Until
This function is to wait until the callback retuning a valid value.
import { setTimeout as wait } from 'timers/promises';
/**
* wait until
* @param callback
* @param options
* @returns
*/
export const waitUntil = <T = any>(
callback: () => Promise<T> | T,
options?: {
/**
* timeout
* @default 10s
*/
timeout?: number;
/**
* interval
* @default 300ms
*/
interval?: number;
/**
* verify the returned value is valid
* @default truthy
*/
verify?: (v?: T) => boolean;
/**
* limit the callback's running times
* @default Infinity
*/
maxTimes?: number;
},
) => {
const { timeout, maxTimes, verify, interval } = options || {};
let hasTimeout = false;
const limit = maxTimes || Infinity;
let times = 0;
let v: T;
let e: Error;
return new Promise<T>(async (resolve, reject) => {
const handler = setTimeout(() => {
hasTimeout = true;
e = new Error(`timeout: ${(timeout || 10000) / 1000}s`);
reject(e);
}, timeout || 10000);
while (!hasTimeout && !v && !e) {
if (times >= limit) {
e = new Error(`max times: ${times}`);
break;
}
times += 1;
try {
v = await callback();
} catch (_e) {
e = _e;
break;
}
if (verify?.(v) || !!v) {
clearTimeout(handler);
return resolve(v);
}
await wait(interval || 300);
}
if (e) {
clearTimeout(handler);
return reject(e);
}
return resolve(v);
});
};
useLongPull
Running a long pull task based on the React Hook.
import { useEffect, useCallback, useRef } from 'react';
/**
* start a long pull task
* - callback changed/clear/stop => stop the task
* - only one task at the same time
* @param callback
* @returns [start, stop]
*/
export const useLongPull = <T = any>(callback: (e?: Error, v?: T) => T | undefined) => {
const ref = useRef<boolean>(false);
const timer = useRef<number>(0);
const continuePull = useCallback((cp: boolean) => {
ref.current = cp;
}, []);
const stop = useCallback(() => continuePull(false), [continuePull]);
const pull = useCallback(
(flag: number) => {
const fn = async (__e?: Error, v?: T) => {
let res;
let e;
try {
res = await callback(__e, v);
} catch (_e) {
e = _e;
}
if (ref.current && flag === timer.current) queueMicrotask(() => void fn(e, res));
};
return fn();
},
[callback],
);
const start = useCallback(() => {
timer.current += 1;
continuePull(true);
pull(timer.current);
}, [pull, continuePull]);
useEffect(() => {
stop();
return stop;
}, [stop]);
return [start, stop] as const;
};
XOR
Limit Concurrent Volume
This class is used to register a task and fetch its result asynchronously.
enum TaskStatus {
waiting,
running,
success,
fail
}
export class TaskService {
/**
* current tasks queue
*/
private tasks: {
id: string;
status: TaskStatus;
task: () => any;
ret?: any;
}[] = [];
/**
* max concurrent volume
*/
private maxConcurrent: number;
/**
* if remove the finished task
*/
private removeFinished = true;
constructor(maxConcurrent: number, removeFinished?: boolean) {
this.maxConcurrent = maxConcurrent;
if (removeFinished !== undefined) this.removeFinished = removeFinished;
}
/**
* execute a waiting task
*/
private execute() {
setTimeout(async () => {
if (this.getCurrentConcurrentVolume() < this.maxConcurrent) {
const item = this.tasks.filter(({ status }) => status === TaskStatus.waiting).pop();
if (item) {
item.status = TaskStatus.running;
try {
const ret = await item.task();
item.ret = ret;
item.status = TaskStatus.success;
} catch (e) {
item.ret = e;
item.status = TaskStatus.fail;
}
this.execute();
}
}
});
}
/**
* get current concurrent volume
* @returns
*/
getCurrentConcurrentVolume() {
return this.tasks.filter(({ status }) => status === TaskStatus.running).length;
}
/**
* register a task
* @param data
* @param taskId
* @returns
*/
register<T extends (() => any)>(task: T) {
const id = `${Math.random()}`;
this.tasks.unshift({
id,
task,
status: TaskStatus.waiting,
});
this.execute();
return id;
}
/**
* search task result
* - if it has been finished, remove it
* @param id
*/
fetch(id: string) {
const index = this.tasks.findIndex(item => item.id === id);
if (index < 0) throw new Error(`invalid id: ${id}`);
const item = this.tasks[index];
if (this.removeFinished && item.status === TaskStatus.success || item.status === TaskStatus.fail) this.tasks.splice(index, 1);
return item;
}
}
setTimeout
/**
* 计时
* @param cb timer: 计时次数
* @param interval 计时间隔
* @returns [start, stop]
*/
export const withTimeChange = (cb: (timer: number) => void, interval = 1000) => {
let timer = 0;
let handler: number | undefined;
let fn: ((last?: number | undefined) => number) | undefined = (last?: number) =>
requestAnimationFrame((t) => {
if (!last || t - last > interval) {
if (last) timer += ((t - last) / interval) >> 0;
else timer += 1;
try {
cb(timer);
} catch (e) {
console.log('callback error in withTimeChange:', e);
}
handler = fn?.(t);
} else {
handler = fn?.(last);
}
});
return [
() => {
handler = fn?.();
},
() => {
if (handler !== undefined) cancelAnimationFrame(handler);
fn = undefined;
},
] as const;
};
Priority Queue
class PriorityQueue<T = any> {
private data: T[];
private sortFn = (a: T, b: T) => a > b;
/**
* @param data data source
* @param sortFn compare value
*/
constructor(params?: {
data?: T[];
sortFn?: (a: T, b: T) => boolean
}) {
const { data, sortFn } = params || {};
if (sortFn) this.sortFn = sortFn;
this.data = data || [];
this.buildHeap();
}
/**
* rebuild the max-heap
*/
private buildHeap() {
// start from the first parent node
for (let i = Math.floor(this.data.length / 2) - 1; i >= 0; i--) this.heapify(this.data.length, i);
}
/**
* swap value
* @param i1 index 1
* @param i2 index 2
*/
private swap(i1: number, i2: number) {
const v = this.data[i1];
this.data[i1] = this.data[i2];
this.data[i2] = v;
}
/**
* To heapify a subtree rooted with node i
* @param n within Array length
* @param i node index
*/
private heapify(n: number, i: number) {
const l = 2 * i + 1;
const r = l + 1;
let largest = i;
if (l < n && this.sortFn(this.data[l], this.data[largest])) largest = l;
if (r < n && this.sortFn(this.data[r], this.data[largest])) largest = r;
if (largest !== i) {
this.swap(i, largest);
// Recursively heapify the affected sub-tree
this.heapify(n, largest);
}
}
/**
* up an node
* @param index
*/
private up(index: number) {
while (index !== 0) {
const parentIndex = Math.floor(index / 2) - 1;
if (!this.sortFn(this.data[index], this.data[parentIndex])) break;
this.heapify(this.data.length, parentIndex);
index = parentIndex;
}
}
/**
* add a new value to the queue
* @param v
*/
add(v: T) {
this.data.push(v);
this.up(this.data.length - 1)
}
/**
* peek and remove the first value
* @returns
*/
peek() {
const v = this.data.shift();
this.heapify(this.data.length, 0);
return v;
}
/**
* remove a specific value
* @param target
*/
remove(target: T) {
const index = this.data.findIndex(v => v === target);
if (index > -1) {
this.data.splice(index, 1);
this.buildHeap();
}
}
/**
* iterate the queue
* @param args
*/
forEach(...args: Parameters<typeof this.data.forEach>) {
this.data.forEach(...args);
}
/**
* get the queue's length
* @returns
*/
size() {
return this.data.length;
}
}