Funzioni JavaScript: progettare API flessibili con rest parameters e currying

by Anastasia P.
SHARE
Funzioni JavaScript: progettare API flessibili con rest parameters e currying
© Guida-HTML5.it

Introduzione

Le funzioni JavaScript possono essere dichiarate in modi diversi: tramite function declaration, function expression oppure arrow function. Questa distinzione non riguarda soltanto lo stile di scrittura: cambia il comportamento del codice, la disponibilità della funzione durante l’esecuzione e il modo in cui possiamo costruire API flessibili.

In questo tutorial useremo questi tre tipi di funzione per realizzare un piccolo sistema di calcolo dei costi di un ordine. L’obiettivo pratico è creare funzioni capaci di:

  • ricevere un numero variabile di argomenti con il parametro rest;
  • configurare progressivamente un calcolo tramite currying;
  • separare la configurazione dalla logica di esecuzione;
  • scegliere consapevolmente tra dichiarazioni, espressioni e arrow functions.

Il currying consiste nel trasformare una funzione che richiede più argomenti in una sequenza di funzioni, ognuna delle quali riceve un solo argomento. Per esempio, una funzione del tipo calcola(tassa, spedizione, totale) può diventare calcola(tassa)(spedizione)(totale).

Codice completo

// 1. Function declaration:
// utile per descrivere una regola principale dell´applicazione.
function sommaImporti(...importi) {
  return importi.reduce((totale, importo) => totale + importo, 0);
}

// 2. Function expression:
// la funzione viene assegnata a una costante.
// Non può essere usata prima di questa riga.
const calcolaSconto = function (percentuale, totale) {
  if (percentuale < 0 || percentuale > 100) {
    throw new RangeError("La percentuale deve essere tra 0 e 100");
  }

  return totale - (totale * percentuale) / 100;
};

// 3. Arrow function:
// sintassi compatta, adatta alle trasformazioni brevi.
const aggiungiSpedizione = (totale, costoSpedizione) => {
  if (costoSpedizione < 0) {
    throw new RangeError("Il costo di spedizione non può essere negativo");
  }

  return totale + costoSpedizione;
};

// 4. Funzione curried:
// prima riceve la percentuale di sconto,
// poi il costo di spedizione,
// infine gli articoli dell´ordine.
const creaCalcolatoreOrdine = (percentualeSconto) =>
  (costoSpedizione) =>
    (...prezzi) => {
      const subtotale = sommaImporti(...prezzi);
      const totaleScontato = calcolaSconto(
        percentualeSconto,
        subtotale
      );

      return aggiungiSpedizione(
        totaleScontato,
        costoSpedizione
      );
    };

// Configurazione riutilizzabile per un negozio online
const calcolaOrdineStandard = creaCalcolatoreOrdine(10)(5);

// Ogni invocazione riceve soltanto i prezzi dei prodotti
const primoTotale = calcolaOrdineStandard(20, 15, 10);
const secondoTotale = calcolaOrdineStandard(50, 30);

console.log(primoTotale);  // 45.5
console.log(secondoTotale); // 77

// Possiamo creare una configurazione diversa
const calcolaOrdinePremium = creaCalcolatoreOrdine(20)(0);

console.log(calcolaOrdinePremium(100, 40)); // 112

Spiegazione

Function declaration

sommaImporti è una dichiarazione di funzione. Questo formato è molto leggibile quando vogliamo esporre una regola importante del dominio, cioè una funzione che rappresenta un’operazione centrale dell’applicazione.

Le dichiarazioni di funzione sono soggette a hoisting: JavaScript rende disponibile la funzione prima della riga in cui è scritta. Per esempio, una chiamata a sommaImporti può teoricamente comparire prima della sua dichiarazione. Anche se il comportamento è valido, è preferibile mantenere l’ordine naturale del codice per evitare confusione.

Il parametro ...importi è un rest parameter. Tutti gli argomenti passati alla funzione vengono raccolti in un array. Questo permette di sommare un numero variabile di prezzi senza creare una funzione diversa per ogni quantità di prodotti.

Function expression

calcolaSconto è una function expression assegnata a una costante. La funzione viene creata e memorizzata nella variabile soltanto quando l’esecuzione raggiunge quella riga.

Di conseguenza, questo codice produrrebbe un errore:

// Errore: calcolaSconto non è ancora inizializzata
// calcolaSconto(10, 100);

const calcolaSconto = function (percentuale, totale) {
  return totale - (totale * percentuale) / 100;
};

Le espressioni sono utili quando vogliamo trattare una funzione come un valore: possiamo assegnarla a una variabile, passarla come argomento o sostituirla con un’altra implementazione.

Arrow function

aggiungiSpedizione e creaCalcolatoreOrdine sono arrow functions. La sintassi è più compatta e funziona particolarmente bene per funzioni brevi, trasformazioni e callback.

La funzione creaCalcolatoreOrdine restituisce una nuova funzione a ogni passaggio:

  • il primo livello riceve la percentuale di sconto;
  • il secondo livello riceve il costo di spedizione;
  • il terzo livello riceve un numero variabile di prezzi.

Le funzioni interne ricordano i valori ricevuti dalle funzioni esterne. Questo comportamento è chiamato closure. Quando creiamo calcolaOrdineStandard, i valori 10 e 5 restano disponibili anche dopo la conclusione della chiamata a creaCalcolatoreOrdine.

Il vantaggio pratico è poter preparare configurazioni riutilizzabili. Il calcolatore standard applica sempre il 10% di sconto e aggiunge 5 euro di spedizione, mentre quello premium applica il 20% senza costi di spedizione.

Best practice

  • Scegli nomi descrittivi: creaCalcolatoreOrdine comunica meglio lo scopo rispetto a un nome generico come factory.
  • Valida gli input: controllare percentuali e costi evita risultati non validi e rende gli errori più semplici da individuare.
  • Mantieni le funzioni focalizzate: ogni funzione dell’esempio svolge un solo compito, come sommare, applicare uno sconto o aggiungere la spedizione.
  • Usa il currying quando migliora la configurazione: è utile se alcuni valori rimangono fissi e altri cambiano spesso. Non conviene applicarlo solo per rendere il codice più sofisticato.
  • Preferisci const per le function expression: impedisce di riassegnare accidentalmente la funzione.
  • Non confondere rest e spread: ...prezzi nella dichiarazione raccoglie gli argomenti; sommaImporti(...prezzi) li espande durante la chiamata.
  • Evita funzioni troppo annidate: se la catena di currying diventa difficile da leggere, una funzione con un oggetto di configurazione può essere più chiara.

Riepilogo

Le function declaration sono adatte alle regole principali e beneficiano dell’hoisting. Le function expression permettono di trattare le funzioni come valori assegnabili e controllabili. Le arrow functions offrono una sintassi compatta e sono ottime per funzioni brevi e composizioni.

Combinando questi strumenti con rest parameters, closure e currying possiamo costruire API semplici da configurare e riutilizzare. Nel nostro esempio abbiamo creato calcolatori d’ordine diversi senza duplicare la logica del calcolo.

Approfondisci con risorse ufficiali

SHARE