personal web client for Bluesky
typescript solidjs bluesky atcute
at trunk 2.2 kB view raw
1// https://github.com/bluesky-social/social-app/blob/019aae5f01cb7b503d242917ae0092c2818f3b71/src/lib/hooks/useToggleMutationQueue.ts 2 3interface Task<TState> { 4 on: boolean; 5 res: (state: TState) => void; 6 rej: (e: unknown) => void; 7} 8 9interface TaskQueue<TState> { 10 curr: Task<TState> | null; 11 next: Task<TState> | null; 12} 13 14export interface ToggleMutationOptions<TState> { 15 initialState: () => TState; 16 mutate: (prevState: TState, nextIsOn: boolean) => Promise<TState>; 17 finalize: (finalState: TState) => void; 18} 19 20export class AbortError extends Error { 21 name = 'AbortError'; 22} 23 24export const createToggleMutationQueue = <TState>({ 25 initialState, 26 mutate, 27 finalize, 28}: ToggleMutationOptions<TState>) => { 29 const queue: TaskQueue<TState> = { 30 curr: null, 31 next: null, 32 }; 33 34 const process = async () => { 35 if (queue.curr) { 36 // There is another active processQueue call iterating over tasks. 37 // It will handle any newly added tasks, so we should exit early. 38 return; 39 } 40 41 // To avoid relying on the rendered state, capture it once at the start. 42 // From that point on, and until the queue is drained, we'll use the real server state. 43 let confirmedState: TState = initialState(); 44 try { 45 while (queue.next) { 46 const prev = queue.curr; 47 const next = queue.next; 48 queue.curr = next; 49 queue.next = null; 50 51 if (prev?.on === next.on) { 52 // Skip multiple requests to update to the same value in a row. 53 prev.rej(new (AbortError as any)()); 54 continue; 55 } 56 57 try { 58 // The state received from the server feeds into the next task. 59 // This lets us queue deletions of not-yet-created resources. 60 confirmedState = await mutate(confirmedState, next.on); 61 next.res(confirmedState); 62 } catch (e) { 63 next.rej(e); 64 } 65 } 66 } finally { 67 finalize(confirmedState); 68 queue.curr = null; 69 queue.next = null; 70 } 71 }; 72 73 const queueToggle = (isOn: boolean): Promise<TState> => { 74 return new Promise((resolve, reject) => { 75 // This is a toggle, so the next queued value can safely replace the queued one. 76 if (queue.next) { 77 queue.next.rej(new (AbortError as any)()); 78 } 79 80 queue.next = { on: isOn, res: resolve, rej: reject }; 81 process(); 82 }); 83 }; 84 85 return queueToggle; 86};