type Action = (...payload: any[]) => void; type RegisteredAction = { action: Action; once: boolean }; const map: { [key: string]: RegisteredAction[] } = {}; const maxQueueTime = 50; const queueInterval = 25; const process = (list: Action[], ...args: any[]) => { const todo = list.concat(); const processItems = (items: Action[]) => { const start = +new Date(); if (items.length > 0) { do { const action = items.shift() || (() => {}); action(...args); } while (items.length > 0 && +new Date() - start < maxQueueTime); } if (items.length > 0) { setTimeout(() => processItems(items), queueInterval); } }; setTimeout(() => processItems(todo), queueInterval); }; export const useEventBus = () => { const add = (key: string, action: Action, once: boolean) => { let list: RegisteredAction[] = []; if (!!map[key]) { list = map[key]; } else { map[key] = list; } list.push({ action, once }); }; const on = (key: string, action: Action) => add(key, action, false); const once = (key: string, action: Action) => add(key, action, true); const off = (key: string, action?: Action) => { let list: RegisteredAction[] = []; if (!!map[key]) { list = map[key]; } if (!!action) { const getIx = () => list.findIndex(x => x.action === action); let ix = getIx(); while (ix > -1) { list.splice(ix, 1); ix = getIx(); } } else { list.length = 0; } if (list.length === 0) { delete map[key]; } }; const emit = (key: string, ...args: any[]) => { const list = map[key] || []; const copy = [...list]; copy.forEach((x, ix) => { if (x.once) { list.splice(ix, 1); } }); const todo = copy.map(x => x.action); if (todo.length > 0) { process(todo, ...args); } }; return { on, once, off, emit, }; };