typescript - Begrens funksjonstype fra union av kall-signaturer etter returtype
Jeg prøver å lage en type som i hovedsak er overbelastning av kallsignatur, men basert på returtypen i stedet for argumentene.
type Callback<T> = (value: T) => void
type CallbackHandler<TResult> = (event: object, cb: Callback<TResult>) => void
type AsyncHandler<TResult> = (event: object) => Promise<TResult>
type Handler<TResult> = CallbackHandler<TResult> | AsyncHandler<TResult>
const cbHandler: Handler<number> = (e1, cb1) => {
// this works
cb1(1)
}
cbHandler({}, () => {})
const asyncHandler: Handler<number> = async (e2) => {
// here e2 is implicitly typed as `any`
// it shouldn't because the function returns Promise<number>, not void
return 1
}
asyncHandler({}) // this should be callable with 1 argument
Lekeromlenke
Er dette overhodet mulig? Eller kanskje dette burde rapporteres som en TS-feil/funksjonsforespørsel?
Svar #1
Problemet her er at funksjoner med færre parametere kan tildeles til funksjoner med flere parametere, og funksjoner som returnerer ikke-void kan tildeles til funksjoner som returnerer void. Dette er begge tilsiktede oppførsler som er nyttige og typesikre, men de er noen ganger overraskende og har frustrerende konsekvenser.
Dette innebærer at de følgende tildelingene er gyldige:
const f = (event: object) => Promise.resolve(3);
const c: CallbackHandler<number> = f; // okay
const a: AsyncHandler<number> = f; // okay
Og dermed når kompilatoren ser på dette:
const asyncHandler: Handler<number> = async (e2) => { return 1 }
// we'll ignore this error until later ----> ~~
Det ser at asyncHandler kan tildeles både CallbackHandler<number> og AsyncHandler<number> og snevrer det ikke inn. Det betyr at følgende feil er forventet:
asyncHandler({}) // error
Hvis du vil unngå det, bør du sannsynligvis bare annotere asyncHandler som AsyncHandler<number>:
const asyncHandler: AsyncHandler<number> = async (e2) => { return 1 }
asyncHandler({}) // okay
Selvfølgelig forklarer det ikke hvorfor det er en implisitt any på e2-parameteren.
Hvis du implementerer en union av funksjonstyper, må du akseptere en union av deres argumenter (merk at dette er den motsatte situasjonen av når du prøver å kalle en union av funksjonstyper og må sende inn en interseksjon av deres argumenter, støtte for dette ble implementert i TypeScript 3.3). Men i stedet for at kompilatoren kontekstuelt typer callback-parameteren som å være av en slik union (som ville være object | object eller bare object i ditt tilfelle), gir kompilatoren bare opp på kontekstuell typifisering og faller tilbake til any.
Og selv om du endrer definisjonene slik at innsnevring faktisk skjer (i stedet for en void-retur, sett noe som er inkompatibelt), har du fortsatt den samme feilen:
const stillAProblem: ((x: string) => number) | ((x: string, y: number) => string) =
x => 3; // error! implicit any
stillAProblem("").toFixed(); // okay
Dette ser ut til å være en designbegrensning eller en feil i TypeScript.
microsoft/TypeScript#6357 tilbake i 2016 sier bare "unions av funksjoner gir deg ikke kontekstuell typetilordning" som om det er et faktum i livet, noe det sannsynligvis var den gangen. Siden den gang støtte microsoft/TypeScript#37580 på dette og kaller det en feil. Og microsoft/TypeScript#41213 støtte også på dette og kaller det en feil. Så kanskje det er en kjent feil. Men jeg vet ikke når eller om det vil bli tatt tak i.
Det mest hensiktsmessige for deg å gjøre, hvis dette var ditt eneste problem, ville være å annotere e2-callback-parameteren eksplisitt:
const asyncHandler: Handler<number> = async (e2: object) => { return 1 }
Men selvfølgelig løser ikke det det opprinnelige problemet fra ovenstående:
asyncHandler({}) // still an error
så mitt forslag her er fortsatt å annotere asyncHandler selv som AsyncHandler<number> og være ferdig med det.
Lenke til kode på lekeplassen
Svar #2
Jeg tror dette er fordi asyncHandler er eksplisitt merket som Handler<number>. Dens definisjon vil ikke innsnevre den til AsyncHandler.
Ta en titt på følgende enkle eksempel:
const foo: object = {bar: 'abc'};
foo.bar; //Property 'bar' does not exist on type 'object'.
Her er for TS foo bare et objekt.
