this repo has no description
at main 5.5 kB view raw
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}