Typescript defines types for parameters used in callbacks for an event bus

Encountering a TypeScript error with our custom event bus:

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

The event bus utilizes unknown[] as an argument for callback functions. Since it's impractical to set every possible type within the arguments of the event bus, we are unsure how to accurately infer the type in TypeScript or if there's an alternative 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 }
  }
}

In our application, we trigger the login event to notify all subscribers:

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

Subsequently, a subscriber is activated:

// 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)
  },
})

The code functions smoothly, but resolving the TS error would be beneficial.

Answer №1

Modify the any extension and set the default to any so that you don't have to specify the type every time.

// 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 }
   }
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Preserve Inference in Typescript Generics When Typing Objects

When utilizing a generic type with default arguments, an issue arises where the inference benefit is lost if the variable is declared with the generic type. Consider the following types: type Attributes = Record<string, any>; type Model<TAttribu ...

Using Vue-router and Typescript with beforeEnter guard - utilizing validated data techniques

As I utilize Vue along with vue-router and typescript, a common scenario arises where a single page is dedicated to displaying a Photo component. A route includes a beforeEnter guard that checks my store to verify the existence of the requested photo. ...

Error TS2322: Cannot assign type 'Promise<Hero | undefined>' to type 'Promise<Hero>'

I am currently studying angular4 using the angular tutorial. Here is a function to retrieve a hero from a service: @Injectable() export class HeroService { getHeroes(): Promise<Hero[]> { return new Promise(resolve => { // ...

Having trouble selecting all checkboxes in the tree using angular2-tree when it first initializes

My goal is to have all checkboxes auto-checked when clicking the "feed data" button, along with loading the data tree. I've attempted using the following code snippet: this.treeComp.treeModel.doForAll((node: TreeNode) => node.setIsSelected(true)); ...

Display an error message in the input type file Form Control if the file format is not .doc or .docx

I need a way to display an alert if the user tries to submit a file that is not of type doc or docx. I've implemented a validator for this purpose and would like the alert message (Unacceptable file type) to be shown when the validation fails. Here i ...

What impact do passing children have on the occurrence of Typescript errors?

Recently, I came across a peculiar situation where the Typescript compiler appeared to be confused by passing the children prop to a component, leading to unsafe behavior. I am looking to create a component that can only accept the subtitle (text) and sub ...

Tips for sorting an array of objects by multiple keys while maintaining the order of each key that comes before

I am looking to create a versatile function that can organize an array of objects based on specified keys, while maintaining the order of previous keys. Here is a sample scenario: const input = [ { a: 'aardvark', b: 'bear', c: 'c ...

Troubleshooting: Prettier Extension Compatibility Issue in VS Code with Create-React-App and Typescript Template

I'm currently in the process of setting up my application using the Create-React-App startup package with the TypeScript template. Everything goes smoothly during the initial installation. However, when I attempt to use the Prettier Code Formatter ext ...

The art of combining Angular 6 with CSS styling for dynamic

Can we dynamically set a value in an scss file from the ts component like demonstrated below? public display: "none" | "block"; ngOnInit(): void { this.display = "none"; } ::ng-deep #clear { display: {{display}} !imp ...

Using Typescript: What is the best way to convert a variable into a specific element of an array?

List of Strings: const myStrings = ["one", "two", "three"]; const newString = "two"; The variable newString is currently just a string, but I would like its type to be an element of myStrings. Is there a way to achi ...

Ways to determine if a date matches today's date within a component template

I am currently displaying a list of news articles on the webpage and I want to show the word "Today" if the news article's date is equal to today's date. Otherwise, I want to display the full date on the page. Is there a way to compare the news.D ...

What is the best way to simulate a dynamoDB call using jest?

In a simple Handler, I have calls to getData defined in a separate file export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => { let respData = await new DynamoDBClient().getData(123); return { status ...

Guide to incorporating a pluck feature into TypeScript code

One task I face frequently is extracting specific properties from an object const obj = {a:1, b: 2, c: 3}; const plucked = pluck(obj, 'a', 'b'); // {a: 1, b:2} Unfortunately, achieving this task with type safety in TypeScript can be c ...

How can I restrict a generic type to include the new() method?

Is there a way to create a function similar to the following in TypeScript? createEntity<TEntity>(): TEntity { return new TEntity(); } In C#, we can achieve this using: void TEntity CreateEntity<TEntity>() where TEntity : new() How would ...

Tips for configuring VS Code to automatically change a callable property to an arrow function instead of a standard function

When interacting with ts/tsx files in VS Code, the autocompletion feature for callable properties offers two options: propertyName and propertyName(args): However, selecting the second option generates a standard function: I would prefer to use an arrow ...

Encountering issues with accessing properties of undefined while chaining methods

When comparing lists using an extension method that calls a comparer, I encountered an error. Here is the code snippet: type HasDiff<T> = (object: T, row: any) => boolean; export const isListEqualToRows = <T>(objects: T[], rows: any[], has ...

Exploring Typescript: Uncovering the Secrets of the navigator.connection Property

I am trying to access the NetworkInformation interface by using a simple TypeScript function like the one shown below: private checkNetworkConnection(): void { const connection = Navigator.connection || navigator.mozConnection || navigator.webkitConn ...

Ways to access an observable's value without causing a new emit event

Is there a way to retrieve the value of an observable without causing it to emit again? I need this value for an ngClass expression. I attempted to use the tap operator within the pipe to access the values in switchMap, but the value is not being logged. ...

Flux Utils identified an issue stating that the Class constructor App cannot be called without the 'new' keyword

Struggling to set up a Flux Util container for this React component: class App extends React.Component<{},AppState> { constructor(props:Readonly<{}>){ super(props); } static getStores(){ return [ArticlesStore]; } static calcul ...

Navigating through an interface array using *ngFor in TypeScript

After successfully implementing an interface to retrieve data from a service class, I encountered an issue when attempting to iterate through the FilteredSubject interface array. Despite using console.log, I was unable to achieve the desired outcome. You ...