import { Subscription } from '../message/Subscription'; import { ConstructorType } from '../types'; import { ValidUtils } from '../valid/ValidUtils'; import { AbortablePromise } from './AbortablePromise'; export namespace Promises { export const isFulfilled = (result: PromiseSettledResult): result is PromiseFulfilledResult => { return result.status === 'fulfilled'; }; export const isRejected = (result: PromiseSettledResult): result is PromiseRejectedResult => { return result.status === 'rejected'; }; export const filterCatch = async (promise: Promise, has: ConstructorType | ((e: any) => boolean) | (ConstructorType | ((e: any) => void))[]) => { try { await promise; return undefined; } catch (e) { const targetHas = Array.isArray(has) ? has : [has]; for (let ha of targetHas) { if (ValidUtils.isConstructor(ha)) { if (e instanceof ha) { return e; } } else { if ((ha as Function)(e)) { return e; } } } throw e; } return undefined; }; export const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); export const sleepReject = (ms: number) => new Promise((resolve, reject) => setTimeout(reject, ms)); export const settle = async (promise: Promise): Promise> => { return Promise.allSettled([promise]).then(results => results[0]); }; export const settles = async (...promises: Promise[]): Promise[]> => { return Promise.allSettled(promises); }; export type SettledResult = PromiseFulfilledResult | (Omit & { reason: E }); export type LoopConfig = { age: number }; export type ObservableLoop = { subscribe: (callback: { then?: (data: T, config: LoopConfig & { duration: number }) => void; catch?: (e: any, config: LoopConfig & { duration: number }) => void; delayThen?: (config: LoopConfig & { duration: number; isCatch: boolean; data?: T }) => void }) => Subscription; }; export const loop = (config: { factory: (config: LoopConfig) => Promise; delay?: number; loopDelay?: number }): ObservableLoop => { return { subscribe: callback => { let stop = false; let age = 0; const loopExecute = async () => { const start = Date.now(); let end = start; age++; const executeConfig = { age: age }; let t: T; let isCatch = false; try { t = await config.factory(executeConfig); end = Date.now(); callback.then?.(t, { ...executeConfig, duration: end - start }); } catch (e) { isCatch = true; end = Date.now(); callback.catch?.(e, { ...executeConfig, duration: end - start }); } await sleep(config.loopDelay ?? 0); // @ts-ignore callback.delayThen?.({ ...executeConfig, duration: end - start, isCatch: isCatch, data: t }); if (!stop) return loopExecute(); }; setTimeout(() => { loopExecute(); }, config.delay ?? 0); return new Subscription(() => { stop = true; }); } }; }; export const delayExecute = (value: (() => T) | (() => Promise), delay = 0): Promise => { return new Promise(resolve => { setTimeout(async () => { const t = await value(); resolve(t); }, delay); }); }; /** * Create an abortable promise chain * @param executor Initial promise or promise factory * @param signal Optional AbortSignal to cancel the execution */ export const abortable = (executor: (() => Promise) | Promise, signal?: AbortSignal): AbortablePromise => { return new AbortablePromise(executor, signal); }; // const { promise, resolve, reject } = Promise.withResolvers(); 공식 이거쓰셈 export const withResolvers = () => { let resolve!: (value: T | PromiseLike) => void; let reject!: (reason?: any) => void; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); return { promise, resolve, reject }; }; export const executeInChunks = async (promiseFactories: (() => Promise)[], config: { chunkSize: number; sleepBetweenChunks: number }): Promise => { const results: T[] = []; const chunkSize = config.chunkSize; const sleepBetweenChunks = config.sleepBetweenChunks; for (let i = 0; i < promiseFactories.length; i += chunkSize) { const chunk = promiseFactories.slice(i, i + chunkSize); const chunkResults = await Promise.all(chunk.map(factory => factory())); results.push(...chunkResults); // Add small delay between chunks to be extra safe if (i + chunkSize < promiseFactories.length) { await sleep(sleepBetweenChunks); } } return results; }; export const executeSettledInChunks = async (promiseFactories: (() => Promise)[], config: { chunkSize: number; sleepBetweenChunks: number }): Promise[]> => { const results: Promises.SettledResult[] = []; const chunkSize = config.chunkSize; const sleepBetweenChunks = config.sleepBetweenChunks; for (let i = 0; i < promiseFactories.length; i += chunkSize) { const chunk = promiseFactories.slice(i, i + chunkSize); const chunkResults = (await Promise.allSettled(chunk.map(factory => factory()))) as Promises.SettledResult[]; results.push(...chunkResults); // Add small delay between chunks to be extra safe if (i + chunkSize < promiseFactories.length) { await sleep(sleepBetweenChunks); } } return results; }; /** * Execute promises with concurrency limit using a pool pattern * @param promiseFactories Array of promise factories * @param concurrency Maximum number of concurrent executions * @returns Promise that resolves with all results */ export const executeWithConcurrency = async (promiseFactories: (() => Promise)[], concurrency: number = 10): Promise => { const results: T[] = new Array(promiseFactories.length); let currentIndex = 0; const executeNext = async (index: number): Promise => { while (currentIndex < promiseFactories.length) { const factoryIndex = currentIndex++; results[factoryIndex] = await promiseFactories[factoryIndex](); } }; // Create pool of concurrent workers const workers = Array(Math.min(concurrency, promiseFactories.length)) .fill(0) .map((_, i) => executeNext(i)); await Promise.all(workers); return results; }; /** * Execute promises with concurrency limit using race pattern (most efficient) * Automatically starts a new promise as soon as one completes * @param promiseFactories Array of promise factories * @param limit Maximum number of concurrent executions (default: 5) * @returns Promise that resolves with all results */ export const executeWithLimit = async (promiseFactories: (() => Promise)[], limit: number = 5): Promise => { const results: T[] = new Array(promiseFactories.length); const executing: Promise[] = []; for (const [index, factory] of promiseFactories.entries()) { const promise = factory() .then(result => { results[index] = result; }) .finally(() => { executing.splice(executing.indexOf(promise), 1); }); executing.push(promise); if (executing.length >= limit) { await Promise.race(executing); } } await Promise.all(executing); return results; }; /** * Retry a promise with configurable delays * @param factory Function that returns a promise to retry (receives attempt number and previous error as parameters) * @param config Retry configuration * @param config.retry Number of retries (0 = no retry, 1 = retry once if failed, etc.) * @param config.delay.initialDelay Delay before first attempt (default: 0) * @param config.delay.retryDelay Delay between retry attempts (default: 0) * @param config.onRetry Callback called on each retry with error and attempt number * @returns Promise that resolves with the result or rejects after all retries * @example * // Retry up to 3 times with 1 second delay between attempts * await Promises.retry((attempt, error) => { * if (error) console.log('Previous error:', error); * return fetchData(attempt); * }, { retry: 3, delay: { retryDelay: 1000 } }) */ export const retry = async ( factory: (attempt: number, error?: any) => Promise, config: { retry: number; delay?: { initialDelay?: number; retryDelay?: number; }; onRetry?: (error: any, attempt: number) => void; } ): Promise => { const initialDelay = config.delay?.initialDelay ?? 0; const retryDelay = config.delay?.retryDelay ?? 0; // Initial delay before first attempt if (initialDelay > 0) { await sleep(initialDelay); } let lastError: any; for (let attempt = 0; attempt <= config.retry; attempt++) { try { return await factory(attempt, lastError); } catch (error) { lastError = error; // Don't retry if this was the last attempt if (attempt === config.retry) { break; } // Call onRetry callback if provided config.onRetry?.(error, attempt + 1); // Wait before retrying if (retryDelay > 0) { await sleep(retryDelay); } } } // All retries failed, throw the last error throw lastError; }; export namespace Result { export type FulfilledType = PromiseFulfilledResult; export type RejectType = Omit & { reason: E }; export type PendingType = { status: 'pending' }; export type Type = FulfilledType | RejectType | PendingType; export type FulfilledState = FulfilledType & { isFulfilled: true; isRejected: false; isPending: false }; export type RejectState = RejectType & { isFulfilled: false; isRejected: true; isPending: false }; export type PendingState = PendingType & { isFulfilled: false; isRejected: false; isPending: true }; export type UndefinedResultState = { status?: undefined; isFulfilled: false; isRejected: false; isPending: false }; export type State = FulfilledState | RejectState | PendingState; export type StateFactory = State & { factory: () => StateFactory }; export type PromiseState = State & Promise; export type PromiseStateFactory = PromiseState & { factory: () => PromiseStateFactory }; export async function awaitWrap(promise: Promise): Promise>; export async function awaitWrap(promise: () => Promise): Promise>; export async function awaitWrap(promise: Promise | (() => Promise)): Promise | StateFactory> { const data = typeof promise === 'function' ? wrap(promise) : wrap(promise); // try { try { await data; } catch (e) {} const rData = { status: (data as any).status, isFulfilled: (data as any).isFulfilled, isRejected: (data as any).isRejected, isPending: (data as any).isPending, value: (data as any).value, reason: (data as any).reason } as any; if (typeof promise === 'function') { rData.factory = () => { return awaitWrap(promise); }; return rData; } else { return rData; } } export function wrap(promise: Promise): PromiseState; export function wrap(promise: () => Promise): PromiseStateFactory; export function wrap(promise: Promise | (() => Promise)): PromiseState | PromiseStateFactory { const promiseResult = (typeof promise === 'function' ? promise() : promise) as State & Promise; if (typeof promise === 'function') { const p = promiseResult as unknown as PromiseStateFactory & Promise; p.factory = () => { return wrap(promise); }; } if (promiseResult.status === undefined) { const p = promiseResult as unknown as PendingState & Promise; p.status = 'pending'; p.isPending = true; p.isFulfilled = false; p.isRejected = false; p.then( result => { console.log('result??', result); const p = promiseResult as unknown as FulfilledState; p.status = 'fulfilled'; p.isFulfilled = true; p.isRejected = false; p.isPending = false; p.value = result; }, reason => { console.log('rejet??', reason); const p = promiseResult as unknown as RejectState; p.status = 'rejected'; p.isFulfilled = false; p.isRejected = true; p.isPending = false; p.reason = reason; } ); } return promiseResult; } } }