Pompei Tech
Guide · typescript

Tipi callable e costruibili

Tipi callable e costruibili

Pensa a un distributore automatico di bevande. Ha un'interfaccia molto precisa: accetta monete di certi tagli, un pulsante per ogni prodotto, ed eroga una lattina in cambio. Non importa che marca di distributore sia, o quale azienda l'abbia costruito: se rispetta quell'interfaccia (accetta quelle monete, ha quei pulsanti, eroga quel prodotto), tu sai esattamente come interagirci. Un "tipo callable" in TypeScript è esattamente questo: la descrizione di come si "interagisce" con una funzione — quali "monete" (argomenti) accetta, e cosa "eroga" (il valore di ritorno) — indipendentemente da come è stata implementata internamente.

Finora abbiamo trattato tipi di argomenti e di ritorno delle funzioni, ma ci sono alcuni concetti più approfonditi che dobbiamo affrontare, tra cui le "firme multiple" (overload) e i tipi callable.

Tipi callable

Sia i type alias che le interfacce offrono la possibilità di descrivere call signature (firme chiamabili):

ts
interface CalcoloDueNumeri {
  (x: numberx: number, y: numbery: number): number;
}

type type CalcoloDueNumeriAlias = (x: number, y: number) => numberCalcoloDueNumeriAlias = (x: numberx: number, y: numbery: number) => number;

const 
const somma: CalcoloDueNumeri
somma
: CalcoloDueNumeri = (a: numbera, b: numberb) => a: numbera + b: numberb;
const
const sottrai: CalcoloDueNumeriAlias
sottrai
: type CalcoloDueNumeriAlias = (x: number, y: number) => numberCalcoloDueNumeriAlias = (x: numberx, y: numbery) => x: numberx - y: numbery;

Fermiamoci un attimo a notare:

  • Il tipo di ritorno per un'interfaccia è : number, mentre per il type alias è => number.
  • Dato che forniamo i tipi per le funzioni somma e sottrai tramite l'annotazione sulla costante, non serve fornire annotazioni di tipo per la lista degli argomenti o per il valore di ritorno di ciascuna singola funzione: TypeScript le deduce dal contesto.

void1

A volte le funzioni non restituiscono nulla, e sappiamo per esperienza con JavaScript che quello che succede davvero nella situazione qui sotto è che x sarà undefined:

ts
function function stampaJSONFormattato(obj: string[]): voidstampaJSONFormattato(obj: string[]obj: string[]) {
  var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v22.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v22.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
(var JSON: JSON
An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.
JSON
.JSON.stringify(value: any, replacer?: (number | string)[] | null, space?: string | number): string (+1 overload)
Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
@paramvalue A JavaScript value, usually an object or array, to be converted.@paramreplacer An array of strings and numbers that acts as an approved list for selecting the object properties that will be stringified.@paramspace Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.@throws{TypeError} If a circular reference or a BigInt value is found.
stringify
(obj: string[]obj, null, " "));
} const
const x: void
x
= function stampaJSONFormattato(obj: string[]): voidstampaJSONFormattato(["ciao", "mondo"]);

Perché compare come void, allora?

void è un tipo speciale, usato specificamente per descrivere i valori di ritorno delle funzioni. Ha il seguente significato:

Il valore di ritorno di una funzione che restituisce void è pensato per essere ignorato.

Potremmo tipizzare le funzioni come se restituissero undefined, ma ci sono alcune differenze interessanti che spiegano il motivo dell'esistenza di void:

ts
function function invocaTraQuattroSecondi(callback: () => undefined): voidinvocaTraQuattroSecondi(callback: () => undefinedcallback: () => undefined) {
  function setTimeout<[]>(callback: () => void, delay?: number): NodeJS.Timeout (+2 overloads)
Schedules execution of a one-time `callback` after `delay` milliseconds. The `callback` will likely not be invoked in precisely `delay` milliseconds. Node.js makes no guarantees about the exact timing of when callbacks will fire, nor of their ordering. The callback will be called as close as possible to the time specified. When `delay` is larger than `2147483647` or less than `1` or `NaN`, the `delay` will be set to `1`. Non-integer delays are truncated to an integer. If `callback` is not a function, a `TypeError` will be thrown. This method has a custom variant for promises that is available using `timersPromises.setTimeout()`.
@sincev0.0.1@paramcallback The function to call when the timer elapses.@paramdelay The number of milliseconds to wait before calling the `callback`. **Default:** `1`.@paramargs Optional arguments to pass when the `callback` is called.@returnsfor use with `clearTimeout()`
setTimeout
(callback: () => undefinedcallback, 4000);
} function function invocaTraCinqueSecondi(callback: () => void): voidinvocaTraCinqueSecondi(callback: () => voidcallback: () => void) { function setTimeout<[]>(callback: () => void, delay?: number): NodeJS.Timeout (+2 overloads)
Schedules execution of a one-time `callback` after `delay` milliseconds. The `callback` will likely not be invoked in precisely `delay` milliseconds. Node.js makes no guarantees about the exact timing of when callbacks will fire, nor of their ordering. The callback will be called as close as possible to the time specified. When `delay` is larger than `2147483647` or less than `1` or `NaN`, the `delay` will be set to `1`. Non-integer delays are truncated to an integer. If `callback` is not a function, a `TypeError` will be thrown. This method has a custom variant for promises that is available using `timersPromises.setTimeout()`.
@sincev0.0.1@paramcallback The function to call when the timer elapses.@paramdelay The number of milliseconds to wait before calling the `callback`. **Default:** `1`.@paramargs Optional arguments to pass when the `callback` is called.@returnsfor use with `clearTimeout()`
setTimeout
(callback: () => voidcallback, 5000);
} const const valori: number[]valori: number[] = []; function invocaTraQuattroSecondi(callback: () => undefined): voidinvocaTraQuattroSecondi(() => valori.push(4));
Type 'number' is not assignable to type 'undefined'.
function invocaTraCinqueSecondi(callback: () => void): voidinvocaTraCinqueSecondi(() => const valori: number[]valori.Array<number>.push(...items: number[]): number
Appends new elements to the end of an array, and returns the new length of the array.
@paramitems New elements to add to the array.
push
(4)); // questa invece va bene

Succede che Array.prototype.push restituisce un numero, e la nostra funzione invocaTraQuattroSecondi qui sopra non è contenta di ricevere questo valore restituito dalla callback — mentre invocaTraCinqueSecondi, dichiarando void, accetta serenamente qualsiasi cosa venga restituita, perché ha dichiarato fin da subito "non mi interessa cosa restituisci".

Construct signature

Le construct signature (firme di costruzione) sono simili alle call signature, tranne per il fatto che descrivono cosa dovrebbe succedere quando la keyword new viene usata in uno scenario di istanziazione.

ts
interface CostruttoreDiData {
  new (valore: numbervalore: number): Date;
}

let let MioCostruttoreDiData: CostruttoreDiDataMioCostruttoreDiData: CostruttoreDiData = var Date: DateConstructor
Enables basic storage and retrieval of dates and times.
Date
;
const
const d: Date
d
= new
let MioCostruttoreDiData: CostruttoreDiData
new (valore: number) => Date
MioCostruttoreDiData
(1697923072611);

Sono rare, ma se mai dovessi incontrarle, ora sai cosa sono.

Overload di funzione

Immagina la seguente situazione:

html
<iframe src="https://example.com" />
<form>
  <input type="text" name="nome" />
  <input type="text" name="email" />
  <input type="password" name="password" />
  <input type="submit" value="Login" />
</form>

Cosa succederebbe se dovessimo creare una funzione che ci permette di registrare un "listener principale per gli eventi"?

  • Se ci viene passato un elemento form, dovremmo permettere la registrazione di una "callback di submit".
  • Se ci viene passato un elemento iframe, dovremmo permettere la registrazione di una "callback per i postMessage".

Proviamoci:

ts
type type GestoreSubmitForm = (data: FormData) => voidGestoreSubmitForm = (data: FormDatadata: FormData) => void;
type type GestoreMessaggio = (evt: MessageEvent) => voidGestoreMessaggio = (evt: MessageEvent<any>evt: interface MessageEvent<T = any>
The **`MessageEvent`** interface represents a message received by a target object. [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent)
MessageEvent
) => void;
function function gestisciEventoPrincipale(elem: HTMLFormElement | HTMLIFrameElement, handler: GestoreSubmitForm | GestoreMessaggio): voidgestisciEventoPrincipale( elem: HTMLFormElement | HTMLIFrameElementelem: HTMLFormElement | HTMLIFrameElement, handler: GestoreSubmitForm | GestoreMessaggiohandler: type GestoreSubmitForm = (data: FormData) => voidGestoreSubmitForm | type GestoreMessaggio = (evt: MessageEvent) => voidGestoreMessaggio, ) {} const
const mioFrame: HTMLIFrameElement
mioFrame
= var document: Document
**`window.document`** returns a reference to the document contained in the window. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
document
.Document.getElementsByTagName<"iframe">(qualifiedName: "iframe"): HTMLCollectionOf<HTMLIFrameElement> (+4 overloads)
The **`getElementsByTagName`** method of The complete document is searched, including the root node. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/getElementsByTagName)
getElementsByTagName
("iframe")[0];
function gestisciEventoPrincipale(elem: HTMLFormElement | HTMLIFrameElement, handler: GestoreSubmitForm | GestoreMessaggio): voidgestisciEventoPrincipale(const mioFrame: HTMLIFrameElementmioFrame, (val) => {});
Parameter 'val' implicitly has an 'any' type.

Questo non va bene, e in effetti TypeScript se ne accorge subito: dato che handler può essere un GestoreSubmitForm o un GestoreMessaggio, e i due tipi di funzione non condividono una firma comune, il compilatore non riesce nemmeno a inferire il tipo di val nella callback — da qui l'errore. Ma il problema di fondo è più ampio: stiamo permettendo troppe possibilità, incluse cose che non vogliamo supportare (per esempio, usare un HTMLIFrameElement con GestoreSubmitForm, il che non ha molto senso).

Possiamo risolvere questo con gli overload di funzione, dove definiamo più "intestazioni" di funzione che fungono da punti di ingresso verso un'unica implementazione:

ts
type type GestoreSubmitForm = (data: FormData) => voidGestoreSubmitForm = (data: FormDatadata: FormData) => void;
type type GestoreMessaggio = (evt: MessageEvent) => voidGestoreMessaggio = (evt: MessageEvent<any>evt: interface MessageEvent<T = any>
The **`MessageEvent`** interface represents a message received by a target object. [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent)
MessageEvent
) => void;
function function gestisciEventoPrincipale(elem: HTMLFormElement, handler: GestoreSubmitForm): void (+1 overload)gestisciEventoPrincipale( elem: HTMLFormElementelem: HTMLFormElement, handler: GestoreSubmitFormhandler: type GestoreSubmitForm = (data: FormData) => voidGestoreSubmitForm, ): void; function function gestisciEventoPrincipale(elem: HTMLIFrameElement, handler: GestoreMessaggio): void (+1 overload)gestisciEventoPrincipale( elem: HTMLIFrameElementelem: HTMLIFrameElement, handler: GestoreMessaggiohandler: type GestoreMessaggio = (evt: MessageEvent) => voidGestoreMessaggio, ): void; function function gestisciEventoPrincipale(elem: HTMLFormElement, handler: GestoreSubmitForm): void (+1 overload)gestisciEventoPrincipale( elem: HTMLFormElement | HTMLIFrameElementelem: HTMLFormElement | HTMLIFrameElement, handler: GestoreSubmitForm | GestoreMessaggiohandler: type GestoreSubmitForm = (data: FormData) => voidGestoreSubmitForm | type GestoreMessaggio = (evt: MessageEvent) => voidGestoreMessaggio, ) {} const const mioFrame: HTMLIFrameElementmioFrame = var document: Document
**`window.document`** returns a reference to the document contained in the window. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
document
.Document.getElementsByTagName<"iframe">(qualifiedName: "iframe"): HTMLCollectionOf<HTMLIFrameElement> (+4 overloads)
The **`getElementsByTagName`** method of The complete document is searched, including the root node. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/getElementsByTagName)
getElementsByTagName
("iframe")[0];
const const mioForm: HTMLFormElementmioForm = var document: Document
**`window.document`** returns a reference to the document contained in the window. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
document
.Document.getElementsByTagName<"form">(qualifiedName: "form"): HTMLCollectionOf<HTMLFormElement> (+4 overloads)
The **`getElementsByTagName`** method of The complete document is searched, including the root node. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/getElementsByTagName)
getElementsByTagName
("form")[0];
function gestisciEventoPrincipale(elem: HTMLIFrameElement, handler: GestoreMessaggio): void (+1 overload)gestisciEventoPrincipale(const mioFrame: HTMLIFrameElementmioFrame, (val: MessageEvent<any>val) => {
val: MessageEvent<any>
val
;
}); function gestisciEventoPrincipale(elem: HTMLFormElement, handler: GestoreSubmitForm): void (+1 overload)gestisciEventoPrincipale(const mioForm: HTMLFormElementmioForm, (val: FormDataval) => {
val: FormData
val
;
});

Guarda un po'! Abbiamo effettivamente creato un collegamento tra il primo e il secondo argomento, che permette al tipo dell'argomento della nostra callback di cambiare, in base al tipo del primo argomento di gestisciEventoPrincipale.

Diamo un'occhiata più da vicino alla dichiarazione della funzione:

ts
function gestisciEventoPrincipale(elem: HTMLFormElement, handler: GestoreSubmitForm): void (+1 overload)
gestisciEventoPrincipale
;

Sembrano tre dichiarazioni di funzione, ma in realtà sono due "intestazioni" che definiscono una lista di argomenti e un tipo di ritorno, seguite dalla nostra implementazione originale.

Se guardi con attenzione i tooltip e i suggerimenti di autocompletamento che ottieni dal language server di TypeScript, è chiaro che puoi chiamare solo le due "intestazioni" esposte, lasciando la "terza intestazione + implementazione" sottostante inaccessibile dall'esterno.

Un'ultima cosa importante da notare: la firma della funzione "implementazione" deve essere abbastanza generale da includere tutto ciò che è possibile attraverso la prima e la seconda intestazione esposta. Per esempio, questo non funzionerebbe:

ts
type type GestoreSubmitForm = (data: FormData) => voidGestoreSubmitForm = (data: FormDatadata: FormData) => void;
type type GestoreMessaggio = (evt: MessageEvent) => voidGestoreMessaggio = (evt: MessageEvent<any>evt: interface MessageEvent<T = any>
The **`MessageEvent`** interface represents a message received by a target object. [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent)
MessageEvent
) => void;
function function gestisciEventoPrincipale(elem: HTMLFormElement, handler: GestoreSubmitForm): void (+1 overload)gestisciEventoPrincipale( elem: HTMLFormElementelem: HTMLFormElement, handler: GestoreSubmitFormhandler: type GestoreSubmitForm = (data: FormData) => voidGestoreSubmitForm, ): void; function gestisciEventoPrincipale(
This overload signature is not compatible with its implementation signature.
elem: HTMLIFrameElementelem: HTMLIFrameElement, handler: GestoreMessaggiohandler: type GestoreMessaggio = (evt: MessageEvent) => voidGestoreMessaggio, ): void; function function gestisciEventoPrincipale(elem: HTMLFormElement, handler: GestoreSubmitForm): void (+1 overload)gestisciEventoPrincipale(elem: HTMLFormElementelem: HTMLFormElement) {}

Tipi this

A volte abbiamo una funzione "libera" (non legata a un oggetto) che ha un'idea molto precisa di cosa dovrebbe essere this, nel momento in cui viene invocata.

Per esempio, se avessimo un event listener del DOM per un bottone:

html
<button onclick="mioGestoreClick">Clicca qui!</button>

Potremmo definire mioGestoreClick così:

ts
function function mioGestoreClick(event: Event): voidmioGestoreClick(event: Eventevent: Event) {
  this.disabled = true;
'this' implicitly has type 'any' because it does not have a type annotation.
}

Con l'opzione compilerOptions.noImplicitThis attiva (è inclusa in strict: true, che dovresti sempre usare), TypeScript rifiuta di lasciare che this sia un any implicito, e ci obbliga a dichiararne esplicitamente il tipo.

Per risolvere il problema, dobbiamo dare a questa funzione un tipo per this:

ts
function function mioGestoreClick(this: HTMLButtonElement, event: Event): voidmioGestoreClick(this: HTMLButtonElementthis: HTMLButtonElement, event: Eventevent: Event) {
  this.HTMLButtonElement.disabled: boolean
The **`HTMLButtonElement.disabled`** property indicates whether the control is disabled, meaning that it does not accept any clicks. [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLButtonElement/disabled)
disabled
= true;
} mioGestoreClick(new Event("click"));
The 'this' context of type 'void' is not assignable to method's 'this' of type 'HTMLButtonElement'.

Ora, quando proviamo a invocare direttamente mioGestoreClick sull'ultima riga dello snippet qui sopra, otteniamo un nuovo errore del compilatore. In pratica, non abbiamo fornito il this che questa funzione dichiara di volere.

ts
function function mioGestoreClick(this: HTMLButtonElement, event: Event): voidmioGestoreClick(this: HTMLButtonElementthis: HTMLButtonElement, event: Eventevent: Event) {
  this.HTMLButtonElement.disabled: boolean
The **`HTMLButtonElement.disabled`** property indicates whether the control is disabled, meaning that it does not accept any clicks. [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLButtonElement/disabled)
disabled
= true;
} const const mioBottone: HTMLButtonElementmioBottone = var document: Document
**`window.document`** returns a reference to the document contained in the window. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
document
.Document.getElementsByTagName<"button">(qualifiedName: "button"): HTMLCollectionOf<HTMLButtonElement> (+4 overloads)
The **`getElementsByTagName`** method of The complete document is searched, including the root node. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/getElementsByTagName)
getElementsByTagName
("button")[0];
const
const handlerAgganciato: (event: Event) => void
handlerAgganciato
= function mioGestoreClick(this: HTMLButtonElement, event: Event): voidmioGestoreClick.CallableFunction.bind<(this: HTMLButtonElement, event: Event) => void>(this: (this: HTMLButtonElement, event: Event) => void, thisArg: HTMLButtonElement): (event: Event) => void (+1 overload)
For a given function, creates a bound function that has the same body as the original function. The this object of the bound function is associated with the specified object, and has the specified initial parameters.
@paramthisArg The object to be used as the this object.
bind
(const mioBottone: HTMLButtonElementmioBottone);
const handlerAgganciato: (event: Event) => voidhandlerAgganciato(new var Event: new (type: string, eventInitDict?: EventInit) => Event
The **`Event`** interface represents an event which takes place on an `EventTarget`. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event)
Event
("click")); // versione agganciata: ok
function mioGestoreClick(this: HTMLButtonElement, event: Event): voidmioGestoreClick.CallableFunction.call<HTMLButtonElement, [Event], void>(this: (this: HTMLButtonElement, args_0: Event) => void, thisArg: HTMLButtonElement, args_0: Event): void
Calls the function with the specified object as the this value and the specified rest arguments as the arguments.
@paramthisArg The object to be used as the this object.@paramargs Argument values to be passed to the function.
call
(const mioBottone: HTMLButtonElementmioBottone, new var Event: new (type: string, eventInitDict?: EventInit) => Event
The **`Event`** interface represents an event which takes place on an `EventTarget`. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event)
Event
("click")); // ok anche questo

Nota che TypeScript capisce che .bind, .call o .apply faranno sì che il this corretto venga passato alla funzione come parte della sua invocazione.

Best practice per i tipi delle funzioni

Dichiara esplicitamente i tipi di ritorno

TypeScript è in grado di inferire i tipi di ritorno delle funzioni in modo piuttosto efficace, ma questo comportamento "accomodante" può portare a effetti a catena non intenzionali, dove i tipi cambiano in punti imprevisti del tuo codebase.

Considera questo esempio:

ts
type type JSONPrimitive = string | number | boolean | nullJSONPrimitive = string | number | boolean | null;
type 
type JSONObject = {
    [k: string]: JSONValue;
}
JSONObject
= { [k: stringk: string]: type JSONValue = JSONPrimitive | JSONObject | JSONArrayJSONValue };
type type JSONArray = JSONValue[]JSONArray = type JSONValue = JSONPrimitive | JSONObject | JSONArrayJSONValue[]; type type JSONValue = JSONPrimitive | JSONObject | JSONArrayJSONValue = type JSONArray = JSONValue[]JSONArray |
type JSONObject = {
    [k: string]: JSONValue;
}
JSONObject
| type JSONPrimitive = string | number | boolean | nullJSONPrimitive;
export async function
function recuperaDati(url: string): Promise<{
    proprieta: string[];
}>
recuperaDati
(url: stringurl: string) {
const const risposta: Responserisposta = await function fetch(input: string | URL | Request, init?: RequestInit): Promise<Response> (+1 overload)
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch)
fetch
(url: stringurl);
const
const dati: {
    proprieta: string[];
}
dati
= (await const risposta: Responserisposta.Body.json(): Promise<any>
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json)
json
()) as {
proprieta: string[]proprieta: string[]; }; return
const dati: {
    proprieta: string[];
}
dati
;
} function function caricaDati(): voidcaricaDati() {
function recuperaDati(url: string): Promise<{
    proprieta: string[];
}>
recuperaDati
("https://example.com").
Promise<{ proprieta: string[]; }>.then<void, never>(onfulfilled?: ((value: {
    proprieta: string[];
}) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<void>
Attaches callbacks for the resolution and/or rejection of the Promise.
@paramonfulfilled The callback to execute when the Promise is resolved.@paramonrejected The callback to execute when the Promise is rejected.@returnsA Promise for the completion of which ever callback is executed.
then
((
risultato: {
    proprieta: string[];
}
risultato
) => {
var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v22.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v22.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
(
risultato: {
    proprieta: string[];
}
risultato
.proprieta: string[]proprieta.Array<string>.join(separator?: string): string
Adds all the elements of an array into a string, separated by the specified separator string.
@paramseparator A string used to separate one element of the array from the next in the resulting string. If omitted, the array elements are separated with a comma.
join
(", "));
}); }

e se facessimo una modifica apparentemente innocente?

diff
export async function recuperaDati(url: string) {
  const risposta = await fetch(url)
+  if (risposta.ok) {
    const dati = await risposta.json()
    return dati
+  }
}

Vedremo comparire alcuni errori di type checking, ma nel punto in cui la funzione viene invocata, non dove è dichiarata.

Immagina se stessimo passando questo valore attraverso diverse altre funzioni prima di arrivare al punto in cui il type checking ci avvisa di un problema!

ts
type type JSONPrimitive = string | number | boolean | nullJSONPrimitive = string | number | boolean | null;
type 
type JSONObject = {
    [k: string]: JSONValue;
}
JSONObject
= { [k: stringk: string]: type JSONValue = JSONPrimitive | JSONObject | JSONArrayJSONValue };
type type JSONArray = JSONValue[]JSONArray = type JSONValue = JSONPrimitive | JSONObject | JSONArrayJSONValue[]; type type JSONValue = JSONPrimitive | JSONObject | JSONArrayJSONValue = type JSONArray = JSONValue[]JSONArray |
type JSONObject = {
    [k: string]: JSONValue;
}
JSONObject
| type JSONPrimitive = string | number | boolean | nullJSONPrimitive;
async function
function recuperaDati(url: string): Promise<{
    proprieta: string[];
} | undefined>
recuperaDati
(url: stringurl: string) {
const const risposta: Responserisposta = await function fetch(input: string | URL | Request, init?: RequestInit): Promise<Response> (+1 overload)
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch)
fetch
(url: stringurl);
if (const risposta: Responserisposta.Response.ok: boolean
The **`ok`** read-only property of the Response interface contains a Boolean stating whether the response was successful (status in the range 200-299) or not. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/ok)
ok
) {
const
const dati: {
    proprieta: string[];
}
dati
= (await const risposta: Responserisposta.Body.json(): Promise<any>
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json)
json
()) as {
proprieta: string[]proprieta: string[]; }; return
const dati: {
    proprieta: string[];
}
dati
;
} // implicitamente, se risposta.ok è false, non restituiamo nulla! } function function caricaDati(): voidcaricaDati() {
function recuperaDati(url: string): Promise<{
    proprieta: string[];
} | undefined>
recuperaDati
("https://example.com").
Promise<{ proprieta: string[]; } | undefined>.then<void, never>(onfulfilled?: ((value: {
    proprieta: string[];
} | undefined) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<void>
Attaches callbacks for the resolution and/or rejection of the Promise.
@paramonfulfilled The callback to execute when the Promise is resolved.@paramonrejected The callback to execute when the Promise is rejected.@returnsA Promise for the completion of which ever callback is executed.
then
((
risultato: {
    proprieta: string[];
} | undefined
risultato
) => {
var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v22.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v22.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
(risultato.proprieta: string[]proprieta.Array<string>.join(separator?: string): string
Adds all the elements of an array into a string, separated by the specified separator string.
@paramseparator A string used to separate one element of the array from the next in the resulting string. If omitted, the array elements are separated with a comma.
join
(", "));
'risultato' is possibly 'undefined'.
}); }

Se usiamo lo stesso esempio, ma definiamo esplicitamente un tipo di ritorno, il messaggio di errore emerge nel punto di dichiarazione:

ts
type type JSONPrimitive = string | number | boolean | nullJSONPrimitive = string | number | boolean | null;
type 
type JSONObject = {
    [k: string]: JSONValue;
}
JSONObject
= { [k: stringk: string]: type JSONValue = JSONPrimitive | JSONObject | JSONArrayJSONValue };
type type JSONArray = JSONValue[]JSONArray = type JSONValue = JSONPrimitive | JSONObject | JSONArrayJSONValue[]; type type JSONValue = JSONPrimitive | JSONObject | JSONArrayJSONValue = type JSONArray = JSONValue[]JSONArray |
type JSONObject = {
    [k: string]: JSONValue;
}
JSONObject
| type JSONPrimitive = string | number | boolean | nullJSONPrimitive;
async function
function recuperaDati(url: string): Promise<{
    proprieta: string[];
}>
recuperaDati
(url: stringurl: string): Promise<{ proprieta: string[] }> {
Function lacks ending return statement and return type does not include 'undefined'.
const const risposta: Responserisposta = await function fetch(input: string | URL | Request, init?: RequestInit): Promise<Response> (+1 overload)
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch)
fetch
(url: stringurl);
if (const risposta: Responserisposta.Response.ok: boolean
The **`ok`** read-only property of the Response interface contains a Boolean stating whether the response was successful (status in the range 200-299) or not. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/ok)
ok
) {
const
const dati: {
    proprieta: string[];
}
dati
= (await const risposta: Responserisposta.Body.json(): Promise<any>
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json)
json
()) as {
proprieta: string[]proprieta: string[]; }; return
const dati: {
    proprieta: string[];
}
dati
;
} } function function caricaDati(): voidcaricaDati() {
function recuperaDati(url: string): Promise<{
    proprieta: string[];
}>
recuperaDati
("https://example.com").
Promise<{ proprieta: string[]; }>.then<void, never>(onfulfilled?: ((value: {
    proprieta: string[];
}) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<void>
Attaches callbacks for the resolution and/or rejection of the Promise.
@paramonfulfilled The callback to execute when the Promise is resolved.@paramonrejected The callback to execute when the Promise is rejected.@returnsA Promise for the completion of which ever callback is executed.
then
((
risultato: {
    proprieta: string[];
}
risultato
) => {
var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v22.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v22.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
(
risultato: {
    proprieta: string[];
}
risultato
.proprieta: string[]proprieta.Array<string>.join(separator?: string): string
Adds all the elements of an array into a string, separated by the specified separator string.
@paramseparator A string used to separate one element of the array from the next in the resulting string. If omitted, the array elements are separated with a comma.
join
(", "));
}); }

Dichiarare esplicitamente il tipo di ritorno ci costringe a gestire subito il "percorso mancante" (il caso in cui risposta.ok è false), invece di scoprirlo tre funzioni più in là, magari in un punto del codice scritto da qualcun altro che non ha idea di come sia fatta recuperaDati internamente.

Footnotes

  1. Esiste un concetto nativo di keyword void in JavaScript, ma non è collegato al concetto TypeScript con lo stesso nome. ↩