50 lines
1.9 KiB
TypeScript
50 lines
1.9 KiB
TypeScript
/** A synchronous lock: React state alone cannot prevent two calls in one tick. */
|
|
export class OperationGate {
|
|
private keys = new Set<string>()
|
|
|
|
acquire(key: string): boolean {
|
|
if (this.keys.has(key)) return false
|
|
this.keys.add(key)
|
|
return true
|
|
}
|
|
|
|
release(key: string) { this.keys.delete(key) }
|
|
has(key: string) { return this.keys.has(key) }
|
|
}
|
|
|
|
export type PollResult = { id: string; error: string | null }
|
|
|
|
export function serverOperationError(error: unknown): string {
|
|
if (error && typeof error === "object" && "status" in error && error.status === 403) {
|
|
return "Недостаточно прав для этого действия."
|
|
}
|
|
if (error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError")) {
|
|
return "Время ожидания истекло. Результат операции неизвестен — обновите данные перед повтором."
|
|
}
|
|
if (error instanceof TypeError) return "Нет соединения с сервером. Проверьте подключение и обновите данные."
|
|
return error instanceof Error ? error.message : "Не удалось выполнить действие. Попробуйте ещё раз."
|
|
}
|
|
|
|
/** Bounded polling, preserving one outcome for every requested server. */
|
|
export async function pollServers(
|
|
ids: string[],
|
|
poll: (id: string) => Promise<void>,
|
|
): Promise<PollResult[]> {
|
|
const uniqueIds = [...new Set(ids)]
|
|
const results: PollResult[] = new Array(uniqueIds.length)
|
|
let next = 0
|
|
await Promise.all(Array.from({ length: Math.min(4, uniqueIds.length) }, async () => {
|
|
while (next < uniqueIds.length) {
|
|
const index = next++
|
|
const id = uniqueIds[index]
|
|
try {
|
|
await poll(id)
|
|
results[index] = { id, error: null }
|
|
} catch (error) {
|
|
results[index] = { id, error: serverOperationError(error) }
|
|
}
|
|
}
|
|
}))
|
|
return results
|
|
}
|