Typescript types for arguments of a callback in an event bus

1.3k Views Asked by At

With our home made event bus we receive the following TS error:

TS2345: Argument of type 'unknown' is not assignable to parameter of type 'AccountInfo | undefined'.
  Type 'unknown

The event bus uses type unknown[] as an argument for a callback function. It's of course not possible to set every possible type within the arguments of the event bus. So we're a bit stuck here on how to have TypeScript infer the type correctly of the argument or if there is another solution?

// eventBus.ts
type TCallBack = (...args: unknown[]) => boolean | void

const subscriptions: { [key: string]: TCallBack[] } = {}

interface ISubscription {
  eventName: string
  callback: TCallBack
}

export function unsubscribe({ eventName, callback }: ISubscription) {
  if (!subscriptions[eventName]) { return }
  const index = subscriptions[eventName].findIndex((l) => l === callback)
  if (index < 0) { return }
  subscriptions[eventName].splice(index, 1)
}

export function subscribe({ eventName, callback }: ISubscription) {
  if (!subscriptions[eventName]) { subscriptions[eventName] = [] }
  subscriptions[eventName].push(callback)
  return () => unsubscribe({ eventName, callback })
}

export function publish(eventName: string, ...args: unknown[]) {
  if (!subscriptions[eventName]) { return }
  for (const callback of subscriptions[eventName]) {
    const result = callback(...args)
    if (result === false) { break }
  }
}

At some point in the app we publish the login event to trigger all subscribers:

// authServce.ts
publish('login', account)

After which a subscriber is triggered:

// authStore.ts
export const setAccount = (account?: AuthenticationResult['account']) => {
  if (account) state.account = account
  else state.account = defaultState().account
  console.log('setAccount: ', state.account)
}

subscribe({
  eventName: 'login',
  callback: (account) => {
    setAccount(account)
  },
})

This code works flawless but it would be nice to be able to solve the TS error.

1

There are 1 best solutions below

1
On BEST ANSWER

Extend any and set the default to any (so you don't need to every time specify the type)

// eventBus.ts
type TCallBack<T extends any = any> = (...args: T[]) => boolean | void

const subscriptions: { [key: string]: TCallBack[] } = {}

interface ISubscription<T> {
  eventName: string
  callback: TCallBack<T>
}

export function unsubscribe<T>({ eventName, callback }: ISubscription<T>) {
  if (!subscriptions[eventName]) { return }
  const index = subscriptions[eventName].findIndex((l) => l === callback)
  if (index < 0) { return }
  subscriptions[eventName].splice(index, 1)
}

export function subscribe<T>({ eventName, callback }: ISubscription<T>) {
  if (!subscriptions[eventName]) { subscriptions[eventName] = [] }
  subscriptions[eventName].push(callback)
  return () => unsubscribe({ eventName, callback })
}

export function publish<T>(eventName: string, ...args: T[]) {
  if (!subscriptions[eventName]) { return }
  for (const callback of subscriptions[eventName]) {
    const result = callback(...args)
    if (result === false) { break }
  }
}