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.

Relaterte artikler