Articolo originale: https://www.freecodecamp.org/news/what-is-a-callback-function-in-javascript-js-callbacks-example-tutorial/

In JavaScript ci sono funzioni e metodi di ordine superiore che accettano una funzione come argomento. Queste funzioni usate come argomenti di altre funzioni sono dette funzioni callback.

Cos'è un callback in JavaScript?

Un callback è una funzione passata come argomento di un'altra funzione.

Questo significa che la funzione genitore è solitamente costruita per poter utilizzare qualsiasi tipo di funzione. D'altro canto, la funzione callback è progettata per un caso specifico (o un numero limitato di utilizzi) in cui viene usata la funzione genitore.

Come si crea una funzione callback in JavaScript?

Puoi creare una funzione callback proprio come qualsiasi altra funzione in JavaScript:

function funzioneCallback () {
    
}

La differenza tra una funzione callback e le altre funzioni è il modo in cui viene usata.

Una funzione callback è realizzata appositamente per essere usata come argomento in un'altra funzione.

function unaFunzione(fun) {
    // ...
    fun(a, b, c);
    //...
}

unaFunzione(funzioneCallback);

Quindi per creare una funzioneCallback hai bisogno di conoscere il modo in cui deve essere usata dalla funzione genitore, gli argomenti che accetta e il loro ordine.

Un esempio di funzione callback

Visto che è una cosa che dovrai fare molte volte, adesso scriveremo una funzione callback. Iniziamo!

Il metodo every è una funzione di ordine superiore che è già integrata nel linguaggio JavaScript.

every è un metodo per array e sfrutta una funzione callback per controllare che tutti gli elementi di un array rispondano a un certo criterio.

Alla funzione callback vengono passati tre argomenti: un elemento dell'array, l'indice dell'elemento e l'intero array.

Quindi, la funzione callback dovrebbe avere questo aspetto:

function funzioneCallback(elemento, indice, array) {
    // fai qualcosa
}

Le funzioni callback possono essere semplici o complesse a seconda delle necessità. Per fare un esempio abbiamo bisogno di un po' di contesto.

Come scrivere una funzione callback in JavaScript

Ipotizziamo di lavorare con array e stringhe. Hai bisogno di controllare se un array contiene solo stringhe di esattamente tre lettere maiuscole, diverse tra loro. Inoltre, non devono esserci ripetizioni di stringhe all'interno dell'array.

È una situazione abbastanza complessa, ma è probabile che tu ti imbatta in qualcosa del genere o di una complessità comparabile, quindi è un buon esercizio.

Quando scrivi una funzione come questa con più cose da valutare, puoi affrontare una condizione alla volta.

La prima condizione è che l'elemento sia una stringa, quindi:

function funzioneCallback(elemento, indice, array) {
    
    // verifica che l'elemento sia una stringa
	const nonStringa = typeof elemento !== "string";
    // se non lo è, termina la funzione
    if (nonStringa) {
        return;
    }
    
}

Poi, le stringhe devono essere formate esattamente da 3 lettere maiuscole.

Puoi verificare queste tre condizioni separatamente, o puoi valutarle tutte insieme con un'espressione regolare di questo tipo: /^[A-Z]{3}$/.

Analizziamo le parti di questa espressione regolare:

  • Il carattere ^ all'inizio e il carattere $ alla fine sono delle ancore. Dicono che la stringa deve iniziare e terminare esattamente in quel modo. Se le usiamo entrambe, vuol dire che la stringa deve contenere soltanto il pattern dell'espressione regolare.
  • [A-Z] è una classe di caratteri che corrisponde a tutte le lettere dalla A alla Z, quindi tutte le lettere maiuscole.
  • {3} è un contatore. Vuol dire che le condizioni precedenti devono essere valide esattamente per tre volte consecutive.

L'espressione regolare che abbiamo appena spiegato è equivalente all'espressione regolare: /^[A-Z][A-Z][A-Z]$/.

In questo caso, invece del contatore {3} abbiamo scritto la classe [A-Z] tre volte.

Aggiungiamola al codice.

function funzioneCallback(elemento, indice, array) {
    
    // verifica che l'elemento sia una stringa
	const nonStringa = typeof elemento !== "string";
    // se non lo è, termina la funzione
    if (nonStringa) {
        return;
    }
        
    // verifica che la stringa sia lunga 3 caratteri e formata solo da lettere maiuscole
    const sonoTreLettereMaiuscole = /^[A-Z]{3}$/.test(elemento);
    // altrimenti, termina la funzione
    if (!sonoTreLettereMaiuscole) {
        return;
    }
    
}

Se non ti piacciono le espressioni regolari, puoi leggere in questa sezione come valutare le stesse condizioni senza usare un'espressione regolare.

Poi, dobbiamo verificare che tutti i caratteri siano diversi.

Ci sono tre caratteri, quindi tre confronti da effettuare: elemento[0] !== elemento[1] && elemento[0] !== elemento[2] && elemento[1] !== elemento[2].

Ma puoi svolgere questa operazione anche con un loop – un doppio loop in realtà.

// nell loop esterno hai j, il primo indice da confrontare
for (let j = 0; j++; j < elemento.length) {
    // nel loop interno hai k, il secondo indice da confrontare
    for (let k = j+1; k++; k < elemento.length) {
        // confronta l'elemento di indice j con l'elemento di indice k
        if (elemento[j] === elemento[k]) {
            // se sono uguali, esegui return per fermare la funzione
            return;
        }
    }
}

Il loop funzionerà per qualsiasi lunghezza, quindi non dovrai riscriverlo per situazioni diverse.

È proprio come scrivere le tre espressioni di confronto? Seguiamo il loop per verificarlo.

Alla prima iterazione abbiamo j=0, e k=1, quindi il primo confronto è elemento[0] === elemento[1]. Poi k aumenta, quindi j=0 e k=2, e abbiamo elemento[0] === elemento[2].

A questo punto, il loop interno si interrompe e il loop esterno (quello con j) passa all'iterazione successiva. Stavolta j=1, e il loop interno parte da k=j+1, ovvero k=2 – il confronto è elemento[1] === elemento[2].

Il loop interno ha terminato le iterazioni, mentre il loop esterno passa da j=1 a j=2. Il loop interno non parte poiché k = j+1 = 3 non rispetta la condizione del loop k < elemento.length.

function funzioneCallback(elemento, indice, array) {
    
    // verifica che l'elemento sia una stringa
	const nonStringa = typeof elemento !== "string";
    // se non lo è, termina la funzione
    if (nonStringa) {
        return;
    }
        
    // verifica che la stringa sia lunga 3 caratteri e formata solo da lettere maiuscole
    const sonoTreLettereMaiuscole = /^[A-Z]{3}$/.test(elemento);
    // altrimenti, termina la funzione
    if (!sonoTreLettereMaiuscole) {
        return;
    }
    
    
    // verifica che tutti i caratteri siano diversi
    const tuttiCaratteriDiversi = elemento[0] !== elemento[1] && elemento[0] !== elemento[2] && elemento[1] !== elemento[2];
    // altrimenti, esegui return per fermare la funzione
    if (!tuttiCaratteriDiversi) {
        return;
    }
    
    
    
}

L'ultima cosa che dobbiamo verificare è che le stringhe non si ripetano nell'array.

Possiamo usare indexOf per controllare che la stringa corrente corrisponda alla prima volta che l'elemento è presente nell'array.

Dobbiamo fare rifermento all'array per questa verifica. E possiamo, visto che uno degli argomenti passati nella funzione callback è il parametro array.

Se questa è la prima volta che la stringa compare nell'array, l'output di indexOf sarà uguale a indice.

Se  array.indexOf(elemento) === indice è true, vuol dire che l'elemento è presente per la prima volta nell'array all'indice indice. Se è false, una stringa identica è presente precedentemente nell'array.

Aggiungiamo il tutto alla funzione, e se la stringa passa tutte le verifiche, alla fine la funzione può restituire true.

function funzioneCallback(elemento, indice, array) {
    
    // verifica che l'elemento sia una stringa
	const nonStringa = typeof elemento !== "string";
    // se non lo è, termina la funzione
    if (nonStringa) {
        return;
    }
        
    // verifica che la stringa sia lunga 3 caratteri e formata solo da lettere maiuscole
    const sonoTreLettereMaiuscole = /^[A-Z]{3}$/.test(elemento);
    // altrimenti, termina la funzione
    if (!sonoTreLettereMaiuscole) {
        return;
    }
    
    
    // verifica che tutti i caratteri siano diversi
    const tuttiCaratteriDiversi = elemento[0] !== elemento[1] && elemento[0] !== elemento[2] && elemento[1] !== elemento[2];
    // altrimenti, esegui return per fermare la funzione
    if (!tuttiCaratteriDiversi) {
        return;
    }
    
    
    // verifica che è la prima volta che l'elemento compare nell'array
    const primaVolta = array.indexOf(elemento) === indice;
    // altrimenti, esegui return per fermare la funzione
    if (!primaVolta) {
        return;
    }
    
    
    return true;
}

E se non usiamo un'espressione regolare?

Nel codice che abbiamo scritto sopra, abbiamo usato l'espressione regolare /^[A-Z]{3}$/ per verificare tre condizioni.

Se non vuoi lavorare con un'espressione regolare, puoi usare la proprietà length per controllare che una stringa abbia una certa lunghezza. In questo caso elemento.length === 3, per verificare che la stringa sia lunga tre caratteri.

La stringa deve contenere solamente lettere maiuscole.

Per questo puoi utilizzare charCodeAt. Questo metodo restituisce il codice ASCII di un carattere, e sapendo che le lettere maiuscole hanno un codice ASCII compreso tra 65 e 90, puoi verificare che ci siano soltanto lettere maiuscole.

Ci sono tre numeri da controllare: elemento.charCodeAt(0), elemento.charCodeAt(1), e elemento.charCodeAt(2). Devono essere tutti compresi tra 65 e 90. Si tratta solo di tre caratteri ma possiamo comunque usare un loop, come di seguito:

for (let i = 0; i++; i < elemento.length) {
    // trova il codice ASCII del carattere
    const codice = elemento.charCodeAt(i);
    // verifica che sia fuori dall'intervallo
    if (codice < 65 || codice > 90) {
        // se lo è, esegui return per fermare la funzione
        return;
    }
}

Aggiungiamo questo blocco alla funzione:

function funzioneCallback(elemento, indice, array) {
    
    // verifica che l'elemento sia una stringa
    const nonStringa = typeof elemento !== "string";
    // se non lo è, termina la funzione
    if (nonStringa) {return;}
    
    // verifica che l'elemento sia lungo tre caratteri
    const lunghezzaTre = elemento.length === 3;
    // se non lo è, termina la funzione
    if (!lunghezzaTre) {return;}
    
    // itera sui caratteri
	for (let i = 0; i++; i < elemento.length) {
        // trova il codice ASCII del carattere
        const codice = elemento.charCodeAt(i);
        // verifica che sia nell'intervallo
        if (codice < 65 || codice > 90) {
            // se non lo è, termina la funzione
            return;
        }
    } 
}

Se sei arrivato qui dal link precedente, puoi tornare su e continuare a leggere come concludere la funzione, altrimenti continua pure fino alla fine.

Come usare la funzione callback di esempio

Abbiamo scritto una funzione callback, ma come possiamo usarla?

unArray.every(funzioneCallback);

Puoi anche usare il metodo every all'interno di una funzione callback – magari la funzione callback di un metodo filter.

Man mano che un programma diventa più complesso, è più probabile utilizzare un maggior numero di funzioni callback.

Perché usiamo le funzioni callback in JavaScript?

Le funzioni callback sono una valida caratteristica di JavaScript. Possiamo avere una funzione generica che svolge un'operazione (come every, che controlla che tutti gli elementi di un array corrispondano a una certa condizione, filter, che rimuove gli elementi che non rispondono a un criterio, replace, un metodo per stringhe che accetta una funzione callback per descrivere come sostituire delle parti di una stringa, e via dicendo) e una funzione callback per aggiungere delle particolarità al suo comportamento in situazioni specifiche.

  • filter, in questa situazione, rimuoverà gli elementi specificati dalla funzione callback.
  • every controllerà che gli tutti gli elementi siano come specificati dalla funzione callback.
  • replace sostituirà delle parti di una stringa come specificato dalla funzione callback.

Le funzioni di ordine superiore aggiungono un livello di astrazione al codice. Non sappiamo (e non abbiamo bisogno di sapere) come every controlli tutti gli elementi di un array e verifichi che siano conformi alle condizioni specificate nella funzione callback. Dobbiamo soltanto sapere che il metodo accetta una funzione callback per svolgere questa operazione.

Conclusione

Le funzioni callback vengono passate come argomenti di altre funzioni. Abbiamo visto un esempio di come crearne una, e alcune considerazioni sul perché sono utili.

Grazie per aver letto questo articolo!