Funzioni Valori di Ritorno

I valori di ritorno delle funzioni in TypeScript possono essere tipizzati esplicitamente o inferiti automaticamente dal compilatore. La tipizzazione del return type definisce il contratto di cosa una funzione produce, permettendo al type system di verificare correttezza e garantire che i consumatori della funzione ricevano i tipi attesi.
Fondamenti e Annotazioni Esplicite
Il tipo di ritorno si specifica dopo i parametri con : Type prima delle parentesi graffe del corpo funzione.
// Tipo di ritorno esplicito
function somma(a: number, b: number): number {
return a + b;
}
// Tipo di ritorno string
function saluta(nome: string): string {
return `Ciao, ${nome}!`;
}
// Tipo di ritorno boolean
function maggiorenne(eta: number): boolean {
return eta >= 18;
}
// Tipo di ritorno con oggetto
function creaUtente(nome: string, eta: number): { nome: string; eta: number } {
return { nome, eta };
}
Inferenza del Tipo di Ritorno
TypeScript inferisce automaticamente il tipo di ritorno analizzando tutte le istruzioni return nel corpo della funzione.
// Tipo inferito come number
function moltiplica(a: number, b: number) {
return a * b;
}
// Tipo inferito come string
function concatena(a: string, b: string) {
return a + b;
}
// Tipo inferito come { id: number; nome: string }
function creaRecord(id: number, nome: string) {
return { id, nome };
}
// Inferenza con conditional
function valoroAssoluto(n: number) {
return n >= 0 ? n : -n; // number inferito
}
Void Return Type
Il tipo void indica che una funzione non restituisce alcun valore significativo. È l’equivalente di funzioni che terminano senza return o con return senza valore.
// Funzione void esplicita
function log(messaggio: string): void {
console.log(messaggio);
}
// Void implicito (nessun return)
function stampa(testo: string): void {
console.log(testo);
// return implicito di undefined
}
// Void con return vuoto
function processa(dato: any): void {
if (!dato) return;
console.log(dato);
}
// NOTA: void permette return undefined, ma non altri valori
function test(): void {
return undefined; // OK
// return 42; // ERRORE
}
Never Return Type
Il tipo never indica funzioni che non terminano mai normalmente: lanciano sempre eccezioni o hanno loop infiniti.
// Funzione che lancia sempre errore
function errore(messaggio: string): never {
throw new Error(messaggio);
}
// Loop infinito
function loopInfinito(): never {
while (true) {
console.log("Infinito");
}
}
// Uso pratico: exhaustive checks
type Forma = "cerchio" | "quadrato";
function area(forma: Forma): number {
switch (forma) {
case "cerchio":
return Math.PI * 10 * 10;
case "quadrato":
return 10 * 10;
default:
// Se arriva qui, forma è never
const _exhaustive: never = forma;
throw new Error(`Forma non gestita: ${forma}`);
}
}
Return Types con Union
Funzioni possono restituire valori di tipi diversi usando union types.
// Union type semplice
function converti(valore: string): string | number {
const num = parseInt(valore);
return isNaN(num) ? valore : num;
}
// Union con null/undefined per gestire assenza
function trova(array: number[], target: number): number | undefined {
const index = array.indexOf(target);
return index >= 0 ? index : undefined;
}
// Union complessa
function elabora(input: string): string | number | { error: string } {
if (input === "") {
return { error: "Input vuoto" };
}
const num = parseInt(input);
return isNaN(num) ? input : num;
}
// Discriminated union
type Result<T> = { success: true; data: T } | { success: false; error: string };
function carica(id: string): Result<User> {
if (id === "") {
return { success: false, error: "ID mancante" };
}
return { success: true, data: { id, nome: "Test" } };
}
Promise Return Types
Funzioni async restituiscono sempre Promise, il cui tipo è Promise<T> dove T è il tipo del valore risolto.
// Promise esplicita
async function fetchDati(): Promise<string> {
const response = await fetch("/api/data");
return await response.text();
}
// Promise con oggetto
async function getUtente(id: string): Promise<{ id: string; nome: string }> {
const response = await fetch(`/api/utenti/${id}`);
return await response.json();
}
// Promise con union per gestire errori
async function caricaSicuro(id: string): Promise<User | null> {
try {
const user = await fetchUser(id);
return user;
} catch {
return null;
}
}
// Promise void
async function salva(dato: any): Promise<void> {
await fetch("/api/save", {
method: "POST",
body: JSON.stringify(dato),
});
// Nessun return esplicito
}
Return Types con Generics
Generics permettono return types che dipendono dai parametri di input mantenendo type safety.
// Generic return type
function primo<T>(array: T[]): T | undefined {
return array[0];
}
const num = primo([1, 2, 3]); // number | undefined
const str = primo(["a", "b"]); // string | undefined
// Generic con trasformazione
function mappa<T, U>(array: T[], fn: (item: T) => U): U[] {
return array.map(fn);
}
const numeri = mappa([1, 2, 3], (n) => n.toString()); // string[]
// Generic con constraints
function massimo<T extends { valore: number }>(items: T[]): T | undefined {
if (items.length === 0) return undefined;
return items.reduce((max, item) => (item.valore > max.valore ? item : max));
}
Tuple Return Types
Restituire tuple per ritornare multiple valori correlati con tipi diversi.
// Tuple return type
function divisioneConResto(
dividendo: number,
divisore: number
): [number, number] {
const quoziente = Math.floor(dividendo / divisore);
const resto = dividendo % divisore;
return [quoziente, resto];
}
const [q, r] = divisioneConResto(17, 5);
// Tuple con label (TypeScript 4.0+)
function coordinate(): [x: number, y: number] {
return [10, 20];
}
// Tuple variadic
function zip<T, U>(arr1: T[], arr2: U[]): Array<[T, U]> {
const length = Math.min(arr1.length, arr2.length);
const result: Array<[T, U]> = [];
for (let i = 0; i < length; i++) {
result.push([arr1[i], arr2[i]]);
}
return result;
}
Literal Return Types
Return types possono essere literal types specifici per indicare valori esatti.
// Literal string
function getStatus(): "success" | "error" | "pending" {
return "success";
}
// Literal number
function getPort(): 3000 | 8080 {
return 3000;
}
// Const assertion per literal
function getConfig() {
return {
mode: "production",
port: 3000,
} as const;
}
// Return type: { readonly mode: "production"; readonly port: 3000 }
// Template literal types
function buildUrl(path: string): `https://${string}` {
return `https://example.com${path}`;
}
Conditional Return Types
Return types che dipendono condizionalmente dai tipi di input usando conditional types.
// Conditional return type
type ReturnType<T> = T extends string ? number : string;
function converti<T extends string | number>(input: T): ReturnType<T> {
if (typeof input === "string") {
return parseInt(input) as ReturnType<T>;
}
return String(input) as ReturnType<T>;
}
// Overload per return types diversi
function processa(input: string): string[];
function processa(input: number): number[];
function processa(input: string | number): string[] | number[] {
if (typeof input === "string") {
return input.split("");
}
return [input, input * 2];
}
Type Guards e Return Types
Funzioni che agiscono come type guards hanno return type speciale arg is Type.
// Type predicate
function isString(valore: unknown): valore is string {
return typeof valore === "string";
}
function processa(input: unknown) {
if (isString(input)) {
// input è string qui
console.log(input.toUpperCase());
}
}
// Type guard con oggetti
interface User {
tipo: "user";
nome: string;
}
interface Admin {
tipo: "admin";
nome: string;
permessi: string[];
}
function isAdmin(utente: User | Admin): utente is Admin {
return utente.tipo === "admin";
}
Return Types in Arrow Functions
Arrow functions supportano annotazioni di tipo di ritorno con sintassi leggermente diversa.
// Arrow function con return type
const somma = (a: number, b: number): number => a + b;
// Arrow function multiline
const elabora = (input: string): string => {
const upper = input.toUpperCase();
return upper.trim();
};
// Arrow function con oggetto return
const creaPersona = (
nome: string,
eta: number
): { nome: string; eta: number } => ({
nome,
eta,
});
// Arrow async
const fetchData = async (url: string): Promise<any> => {
const response = await fetch(url);
return await response.json();
};
Return Types in Metodi di Classe
I metodi di classe possono avere return types espliciti o inferiti come funzioni standalone.
class Calcolatrice {
// Return type esplicito
somma(a: number, b: number): number {
return a + b;
}
// Return type inferito
moltiplica(a: number, b: number) {
return a * b; // number inferito
}
// Return type this per method chaining
reset(): this {
return this;
}
// Getter con return type
get valore(): number {
return 0;
}
// Async method
async carica(): Promise<void> {
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
// Method chaining con this
class Builder {
private config: any = {};
setPort(port: number): this {
this.config.port = port;
return this;
}
setHost(host: string): this {
this.config.host = host;
return this;
}
build(): any {
return this.config;
}
}
const config = new Builder().setPort(3000).setHost("localhost").build();
Type Assertions nei Return
Type assertions possono essere usate per restringere o espandere il tipo di ritorno quando necessario.
// Type assertion in return
function getElemento(id: string): HTMLElement {
const elemento = document.getElementById(id);
return elemento as HTMLElement; // Assert non-null
}
// Con casting
function parseJson<T>(json: string): T {
return JSON.parse(json) as T;
}
const user = parseJson<User>('{"nome":"Mario"}');
// Non-null assertion
function trovaUtente(id: string): User {
const utente = database.find(id);
return utente!; // Assert che utente non è null/undefined
}
Index Signatures in Return Types
Return types possono includere oggetti con chiavi dinamiche usando index signatures.
// Return con index signature
function raggruppa(items: string[]): { [key: string]: number } {
const result: { [key: string]: number } = {};
items.forEach((item) => {
result[item] = (result[item] || 0) + 1;
});
return result;
}
// Record type (più type-safe)
function contaggi(items: string[]): Record<string, number> {
return items.reduce((acc, item) => {
acc[item] = (acc[item] || 0) + 1;
return acc;
}, {} as Record<string, number>);
}
// Partial return type
function aggiornaOggetto<T>(obj: T, updates: Partial<T>): T {
return { ...obj, ...updates };
}
Quando Specificare Return Types Espliciti
Specificare return types espliciti è raccomandato per API pubbliche, funzioni complesse, e quando l’inferenza potrebbe essere ambigua o imprecisa.
// Esplicito: API pubblica
export function calcolaPrezzo(prodotti: Prodotto[]): number {
return prodotti.reduce((sum, p) => sum + p.prezzo, 0);
}
// Esplicito: logica complessa
function elaboraDati(input: any): ProcessedData {
// Logica complessa
// Return type esplicito documenta contratto
return processedResult;
}
// Inferenza OK: funzione semplice interna
function double(n: number) {
return n * 2; // number chiaramente inferito
}
Conclusioni
La tipizzazione dei valori di ritorno in TypeScript fornisce contratti chiari per funzioni, garantendo che consumatori ricevano tipi attesi e catturando errori di incompatibilità a compile time. L’inferenza automatica riduce verbosità per casi semplici, mentre annotazioni esplicite documentano intenzioni e migliorano manutenibilità per API pubbliche. Tipi speciali come void, never, Promise, union e generics permettono espressività completa, modellando accuratamente comportamenti da operazioni sincrone semplici a pattern asincroni complessi, assicurando type safety end-to-end nel flusso di dati delle applicazioni.