this repo has no description
1import { err, type Result } from "../types/result.ts";
2
3export function debounce<T extends (...args: Parameters<T>) => void>(
4 fn: T,
5 ms: number,
6): T & { cancel: () => void } {
7 let timeoutId: ReturnType<typeof setTimeout> | null = null;
8
9 const debounced = ((...args: Parameters<T>) => {
10 if (timeoutId) clearTimeout(timeoutId);
11 timeoutId = setTimeout(() => {
12 fn(...args);
13 timeoutId = null;
14 }, ms);
15 }) as T & { cancel: () => void };
16
17 debounced.cancel = () => {
18 if (timeoutId) {
19 clearTimeout(timeoutId);
20 timeoutId = null;
21 }
22 };
23
24 return debounced;
25}
26
27export function throttle<T extends (...args: Parameters<T>) => void>(
28 fn: T,
29 ms: number,
30): T {
31 let lastCall = 0;
32 let timeoutId: ReturnType<typeof setTimeout> | null = null;
33
34 return ((...args: Parameters<T>) => {
35 const now = Date.now();
36 const remaining = ms - (now - lastCall);
37
38 if (remaining <= 0) {
39 if (timeoutId) {
40 clearTimeout(timeoutId);
41 timeoutId = null;
42 }
43 lastCall = now;
44 fn(...args);
45 } else if (!timeoutId) {
46 timeoutId = setTimeout(() => {
47 lastCall = Date.now();
48 timeoutId = null;
49 fn(...args);
50 }, remaining);
51 }
52 }) as T;
53}
54
55export function sleep(ms: number): Promise<void> {
56 return new Promise((resolve) => setTimeout(resolve, ms));
57}
58
59export async function retry<T>(
60 fn: () => Promise<T>,
61 options: {
62 attempts?: number;
63 delay?: number;
64 backoff?: number;
65 shouldRetry?: (error: unknown, attempt: number) => boolean;
66 } = {},
67): Promise<T> {
68 const {
69 attempts = 3,
70 delay = 1000,
71 backoff = 2,
72 shouldRetry = () => true,
73 } = options;
74
75 let lastError: unknown;
76 let currentDelay = delay;
77
78 for (let attempt = 1; attempt <= attempts; attempt++) {
79 try {
80 return await fn();
81 } catch (error) {
82 lastError = error;
83 if (attempt === attempts || !shouldRetry(error, attempt)) {
84 throw error;
85 }
86 await sleep(currentDelay);
87 currentDelay *= backoff;
88 }
89 }
90
91 throw lastError;
92}
93
94export async function retryResult<T, E>(
95 fn: () => Promise<Result<T, E>>,
96 options: {
97 attempts?: number;
98 delay?: number;
99 backoff?: number;
100 shouldRetry?: (error: E, attempt: number) => boolean;
101 } = {},
102): Promise<Result<T, E>> {
103 const {
104 attempts = 3,
105 delay = 1000,
106 backoff = 2,
107 shouldRetry = () => true,
108 } = options;
109
110 let lastResult: Result<T, E> | null = null;
111 let currentDelay = delay;
112
113 for (let attempt = 1; attempt <= attempts; attempt++) {
114 const result = await fn();
115 lastResult = result;
116
117 if (result.ok) {
118 return result;
119 }
120
121 if (attempt === attempts || !shouldRetry(result.error, attempt)) {
122 return result;
123 }
124
125 await sleep(currentDelay);
126 currentDelay *= backoff;
127 }
128
129 return lastResult!;
130}
131
132export function timeout<T>(promise: Promise<T>, ms: number): Promise<T> {
133 return new Promise((resolve, reject) => {
134 const timeoutId = setTimeout(() => {
135 reject(new Error(`Timeout after ${ms}ms`));
136 }, ms);
137
138 promise
139 .then((value) => {
140 clearTimeout(timeoutId);
141 resolve(value);
142 })
143 .catch((error) => {
144 clearTimeout(timeoutId);
145 reject(error);
146 });
147 });
148}
149
150export async function timeoutResult<T>(
151 promise: Promise<Result<T, Error>>,
152 ms: number,
153): Promise<Result<T, Error>> {
154 try {
155 return await timeout(promise, ms);
156 } catch (e) {
157 return err(e instanceof Error ? e : new Error(String(e)));
158 }
159}
160
161export async function parallel<T>(
162 tasks: (() => Promise<T>)[],
163 concurrency: number,
164): Promise<T[]> {
165 const results: T[] = [];
166 const executing: Promise<void>[] = [];
167
168 for (const task of tasks) {
169 const p = task().then((result) => {
170 results.push(result);
171 });
172
173 executing.push(p);
174
175 if (executing.length >= concurrency) {
176 await Promise.race(executing);
177 executing.splice(
178 executing.findIndex((e) => e === p),
179 1,
180 );
181 }
182 }
183
184 await Promise.all(executing);
185 return results;
186}
187
188export async function mapParallel<T, U>(
189 items: T[],
190 fn: (item: T, index: number) => Promise<U>,
191 concurrency: number,
192): Promise<U[]> {
193 const results: U[] = new Array(items.length);
194 const executing: Promise<void>[] = [];
195
196 for (let i = 0; i < items.length; i++) {
197 const index = i;
198 const p = fn(items[index], index).then((result) => {
199 results[index] = result;
200 });
201
202 executing.push(p);
203
204 if (executing.length >= concurrency) {
205 await Promise.race(executing);
206 const doneIndex = executing.findIndex(
207 (e) => (e as Promise<void> & { _done?: boolean })._done !== false,
208 );
209 if (doneIndex >= 0) {
210 executing.splice(doneIndex, 1);
211 }
212 }
213 }
214
215 await Promise.all(executing);
216 return results;
217}
218
219export function createAbortable<T>(
220 fn: (signal: AbortSignal) => Promise<T>,
221): { promise: Promise<T>; abort: () => void } {
222 const controller = new AbortController();
223 return {
224 promise: fn(controller.signal),
225 abort: () => controller.abort(),
226 };
227}
228
229export interface Deferred<T> {
230 promise: Promise<T>;
231 resolve: (value: T) => void;
232 reject: (error: unknown) => void;
233}
234
235export function deferred<T>(): Deferred<T> {
236 let resolve!: (value: T) => void;
237 let reject!: (error: unknown) => void;
238
239 const promise = new Promise<T>((res, rej) => {
240 resolve = res;
241 reject = rej;
242 });
243
244 return { promise, resolve, reject };
245}