Home > Software design >  Parameters conflicting when injecting a function as an argument
Parameters conflicting when injecting a function as an argument

Time:08-19

I am trying to make a function general enough to take any informer and any mapper and apply dependency injection for the logic not to be changed if the informer and mapper were to change, however, it seems like I am doing something wrong about my function argument generalization.

Also if I were to change unknown to any in the myFunc inform signature, it would warn me that any is not advice and is unexpected.

Interfaces

interface Document {
    name: string
}
interface Mapper {
    (doc: Document): unknown
}

Function

const myFunc = async (
    inform: (payload: unknown)=>Promise<Record<string, unknown>>,
    document: Document,
    mapper: Mapper): Promise<string> => {
  const response = <Record<string, unknown>> await inform(mapper(document));
  return response
})

Problem

class APIClient {
   async inform(payload: RequestInfo): Promise<Record<string, unknown>> {
    // Implementation
    }
}
const apiClient = new APIClient();
const result = await myFunc(
              apiClient.inform, //Complaining that unknown cannot be assigned to RequestInfo.
              someData,
              someMapper
          );

CodePudding user response:

unknown is indeed difficult to use as a generalization mean.

And as you figured out, you could use any instead, but that removes any type safety.

You have a use case for generics, where you do not know the exact data types in advance, but you know that there must be some relations between them:

  • mapper must take an argument type Document2 (caution: Document type is already available in global scope of many environments, name collision will give unexpected result, mainly interface merging, so I will use Document2 instead)
  • It must return a type "P" (not known in advance) that inform can take as input.

Therefore you need at least 1 generic type ("P" above):

const myFunc = async <P, R>(
    inform: (payload: P) => Promise<Record<string, R>>, // inform takes P in
    document: Document2,
    mapper: (doc: Document2) => P) => { // mapper outputs P
    const response = await inform(mapper(document));
    return response
}

Here is an example where "P" is string:

const someData: Document2 = {
    name: 'hello'
}
const someMapper = (doc: Document2) => doc.name // Outputs a string

const result = myFunc(
    apiClient.inform, // RequestInfo is string | Request, so a string in is fine!
    someData,
    someMapper
);

Playground Link

  • Related