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 typeDocument2
(caution:Document
type is already available in global scope of many environments, name collision will give unexpected result, mainly interface merging, so I will useDocument2
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
);