Funzioni Valori di Ritorno

Edoardo Midali
Edoardo Midali

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.