this repo has no description
1import { ok, err, type Result } from '../types/result'
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) =>
208 (e as Promise<void> & { _done?: boolean })._done !== false
209 )
210 if (doneIndex >= 0) {
211 executing.splice(doneIndex, 1)
212 }
213 }
214 }
215
216 await Promise.all(executing)
217 return results
218}
219
220export function createAbortable<T>(
221 fn: (signal: AbortSignal) => Promise<T>
222): { promise: Promise<T>; abort: () => void } {
223 const controller = new AbortController()
224 return {
225 promise: fn(controller.signal),
226 abort: () => controller.abort(),
227 }
228}
229
230export interface Deferred<T> {
231 promise: Promise<T>
232 resolve: (value: T) => void
233 reject: (error: unknown) => void
234}
235
236export function deferred<T>(): Deferred<T> {
237 let resolve!: (value: T) => void
238 let reject!: (error: unknown) => void
239
240 const promise = new Promise<T>((res, rej) => {
241 resolve = res
242 reject = rej
243 })
244
245 return { promise, resolve, reject }
246}