Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
FUNCTION ARROW
Ricordiamo che una function expression è una function che viene memorizzata in una variabile, ed ha la sintassi:
var funzione = function() { ... };
questa viene chiamata usando il nome della variabile: funzione().
Una function arrow è una nuova sintassi per definire le function expression in modo più semplice e immediato:
var funzione = () => { ... }
All’interno delle parentesi tonde () è possibile passare parametri separati da virgole:
var somma = (x, y) => x + y;
document.write(somma(5,6)); 11
Notiamo che se si ha una sola istruzione (x + y), non si scrivono le graffe {} e il return.
Se deve essere passato un solo parametro non si scrivono nemmeno le parentesi tonde:
var somma = x => x + x;
document.write(somma(5)); 10
PARAMETRO DI DEFAULT
È possibile indicare un parametro di default in una function, in modo che se non ne viene passato nessuno viene
usato quel valore, altrimenti se viene passato un parametro, questo sovrascrive il parametro di default:
OPERATORE REST
Si usa quando non si sa quanti parametri passare ad una funzione. Si indica con:
...nomeParam
dove nomeParam è un array che andrà a contenere tutti i parametri passati alla function.
function somma(...n) {
return (n.reduce((x,y) => {
return x + y
}));
}
console.log(somma(4,5,1,7,3)); 20
La function reduce() riceve come parametro una funzione, ed applica questa funzione a ogni elemento dell’array,
riducendolo ad un singolo elemento.
Vediamo che la function somma() ha come parametro ...n, che appunto rappresenta un array con gli elementi che
riceve dalla chiamata della function, ritorna la somma di questi.
La function può ricevere altri parametri oltre a questo, quelli sicuri, che vengono considerati fuori dall’array, con:
la function ritornerà 16 e non 20, perchè assegna il primo parametro passato 4 a j e somma gli elementi dell’array n
DESTRUTTURAZIONE E OPERATORE SPREAD
Destrutturare significa scomporre una struttura negli elementi che la costituiscono. Destrutturare un array o un
oggetto, consiste quindi nell’ assegnare gli elementi a singole variabili separate:
let [a, b] = [3,7];
questo esempio assegna 3 alla variabile ‘a’ e 7 a ‘b’.
Per la destrutturazione degli array o degli oggetti esiste l’ operatore di spread che scompatta l’array in elementi
separati. La sintassi dell’operatore spread è la stessa dell’operatore rest, ma esegue esattamente il contrario:
...array
Gli usi più frequenti e utili dell’operatore spread sono per:
Copia di un array
In JavaScript, non è possibile semplicemente copiare un array impostando una nuova variabile, ad esempio:
var arr = [a, b, c];
var arr2 = arr;
console.log(arr2);
Il risultato di arr2 sarebbe [a, b, c], a prima vista, sembra che abbia funzionato, si sono copiati i valori di arr in arr2.
Ma il problema è che in javascript un array è un tipo di oggetto e quando si lavora con oggetti javascript,
l’assegnazione è fatta per riferimento non per valore, quindi ad arr2 è stato assegnato allo stesso riferimento di arr e
tutto ciò che facciamo a arr2 influenzerà anche l’array arr originale (e viceversa).
Per effetturare una copia dell’array si usa l’operatore spread con:
var arr = [a, b, c];
var arr2 = [...arr];
In questo caso il valore di arr è espanso per riempire le parentesi dell’array arr2: si assegna ogni singolo valore
di arr ad arr2 e quindi stiamo lavorando su ogni singolo valore e quindi, non per riferimento.
EREDITARIETA’
Per ereditarietà si intende creare una nuova classe che ne estende un altra, ereditando da questa proprietà e
metodi. Si usa la keyword extends:
class AutoElettrica extends Auto {
constructor(marca, colore, motore) {
super(marca, colore);
this.motore = motore;
}
descrizione() {
return super.descrizione() + “ Motore: “ + this.motore;
}
}
var tesla = new AutoElettrica('tesla', 'grigio', 'elettrico');
console.log(tesla.descrizione());
La classe AutoElettrica estende la classe Auto ereditando da essa proprietà e metodi. Vediamo che, definito il
constructor(), per richiamare al suo interno le proprietà ereditate, senza il bisogno di riscriverle, si usa il metodo
super(), dopo è possibile definire altre proprietà. Per quanto riguarda i metodi, bisogna dichiararli e al loro interno
richiamare il metodo ereditato con super.metodo() e aggiungere o no le modifiche relative alla nuova classe.
METODI STATIC
I metodi definiti con static sono metodi di classe, possono essere invocati senza instanziare oggetti, non sono
disponibili per gli oggetti. Si invocano sempre con il nome della classe seguito dal nome del metodo, eccetto nel caso
in cui una classe estende un’altra classe contenente metodi static, questi possono essere ereditati con
super.metodo().
I metodi statici sono spesso usati per definire funzioni utili in diversi parti dell'applicazione.
CICLO FOR...OF
Il ciclo for...of consente di scorrere strutture di dati iterabili come array, stringhe, mappe, elenchi di nodi e altro
ancora. La sintassi:
for (variable of iterable) {
//...codice...
}
Per ogni iterazione il valore della proprietà successiva viene assegnato alla variabile. Variabile può essere dichiarata
con const, let o var.
iterable - Un oggetto con proprietà iterabili.
PROGRAMMAZIONE ASINCRONA
Quando scriviamo istruzioni in javascript queste vengono eseguite in sequenze, ossia una riga alla volta.
Ciò vuol dire che ogni riga deve essere interamente eseguita prima di procedere a quella successiva, stiamo parlando
di programmazione sincrona.
Nella programmazione asincrona invece si può passare alle righe successive anche se quelle precedenti non hanno
terminato l’esecuzione.
La programmazione asincrona è una forma di programmazione parallela che permette ad un’unità di lavoro di
funzionare separatamente dal thread principale, notificandogli quando avrà finito il lavoro.
Gli approcci più utilizzati per l’esecuzione di operazioni asincrone in JavaScript sono: le function callback e le
Promise introdotte da ES6.
FUNCTION CALLBACK
Le funzioni in Javascript, consentono di passare come parametro non solo dei valori, ma anche altre funzioni che
prenderanno il nome di funzioni di callback (funzioni di richiamo). Tali funzioni di callback, in genere, vengono
eseguite dopo la routine principale, quindi al termine dell'esecuzione primaria lanciata dalla funzione chiamante.
Lo scopo delle funzioni di callback è quello di consentire a funzioni specifiche di svolgere comiti ulteriori a quelli loro
propri, compiti che normalmente non sono noti al momento della scrittura del codice della funzione principale.
// definizione della funzione principale
function esempio(parametro, callback) {
// Routine principale...
// Callback...
if (callback && typeof callback === 'function') callback();
}
PROMISE
Con Javascript ES6 sono state introdotte le Promise per gestire le operazioni asincrone al posto delle callback.
Per capire meglio il concetto delle Promises facciamo un paragone di vita quotidiana: ad esempio quando andiamo
alla Poste non appena entriamo ritiriamo il numero. Se, sfortunatamente abbiamo diverse persone avanti ,cosa
facciamo aspettiamo li oppure ne approfittiamo per fare altro? Se si opta per la seconda soluzione , cioè se nel
frattempo fai altro, stai applicando il principio della programmazione asincrona, in quanto hai ricevuto la promessa
che, quando sarà visualizzato il tuo numero, toccherà a te.
Se questo non avvenisse, nel senso che qualcuno ti passasse avanti, ti arrabbieresti andando in errore.
Una Promise rappresenta un'operazione che non è ancora completata, ma lo sarà in futuro, deve notificare quando
una esecuzione asincrona è terminata, oppure notificare che l’esecuzione della chiamata non è andata a buon fine. Il
parametro resolve serve appunto per dire quando una promise è andata a buon fine, mentre il
parametro reject serve per dire che una esecuzione asincrona NON è andata a buon fine.
La funzione resolve verrà utilizzato se la promessa è mantenuta (successo) e reject verrà usata se la promessa non è
mantenuta (fallimento).
Rappresentano un valore che possiamo gestire in futuro.
Immaginiamo di avere una situazione dove sono preseti molte funzioni f1, f2, f3, … fn dove ogni funzione f i deve
aspettare il completamento della funzione precedente f i-1. Oppure attendere la risposta del server.
Creare una Promise
La sintassi per definire una Promise:
then() e catch()
Questi servono per specificare e gestire cosa fare quando una promise ha successo o fallisce. Ogni promise ha una
funzione then() che sarà eseguita al completamento della promise e se questa ha successo, riceve il valore
proveniente da resolve. Con catch() facciamo una cosa simile a then() ma sarà eseguita se la promise fallisce e riceve
l’errore proveniente da reject.
Esempio
let promiseCompiti = new Promise(function(resolve, reject){
let fatti = false;
if(fatti) {
resolve("Fatti");
} else {
reject("NON fatti");
}
});
promiseCompiti.then(function(risultato) {
console.log("Compiti " + risultato);
}).catch(function(errore) {
console.log("Compiti " + errore);
});
Esempio di promise di finire i compiti, a fine giornata o abbiamo mantenuto la promessa o abbiamo fallito nel
mantenerla.
Vediamo che chiamiamo resolve() se i compiti sono stati fatti (fatti = true) altrimenti chiamiamo reject() se i compiti
non sono stati fatti (fatti = false).
Dopodiche se la promise ha successo (i compiti sono stati fatti), viene chiamato then() che, attraverso una funzione
che riceve come parametro, riceve il risultato da resolve() e stampa a video “Compiti Fatti”, altrimenti se la promise
fallisce (i compiti non sono stati fatti), viene chiamato catch() che, attraverso una funzione che riceve come
parametro, riceve il risultato di errore da reject() e stampa a video “Compiti NON fatti”.
film.php
file che contiene un array di dati film:
<?php
//File php sul server che ritorna un array di dati film
$film = [
['titolo' => 'The Wolf of Wall Street', 'attore' => 'Leonardo Di Caprio', 'anno' => 2013],
['titolo' => 'Interstellar', 'attore' => 'Matthew McConaughey', 'anno' => 2014],
['titolo' => 'Rocky', 'attore' => 'Sylvester Stallone', 'anno' => 1976]
];
//intestazione che indica al client quale sia il tipo di contenuto restituito
header('Content-type:application/json');
echo json_encode($film); //converte l'array in una stringa json
exit;
?>
<!DOCTYPE html>
<html>
<head>
<title>Esempio Ajax con Promise</title>
<meta charset="utf-8" />
<script src="ajax.js"></script>
</head>
<body onload="callServer()">
<!--div che conterrà il contenuto ritornato dal server-->
<div id="content"></div>
</body>
</html>
ajax.js
Vediamo le due versioni: normale Ajax e quella gestita con la promise.
function callServer() {
//Normale esempio di chiamata Ajax al server
let objhttp = new XMLHttpRequest(); //oggetto per creare canale comunicazione con il server
objhttp.open('GET', 'http://localhost/promiseAjax/film.php'); //apre canale asincrono
con il server
objhttp.send(); //invia la richiesta al server
//callback per verificare lo stato della richiesta e accedere al risultato solo se ha
avuto successo
objhttp.onreadystatechange = function() {
if (objhttp.readyState == 4 && objhttp.status == 200) {
let objDati = JSON.parse(objhttp.responseText); //riceve la stringa JSON dal server
e la converte in un oggetto
let ul = '<ul>';
ul += objDati.map((film) => '<li>' + film.titolo + '</li>');
document.getElementById('content').innerHTML = ul;
}
};
//gestione in caso di errore
objhttp.error = () => {
alert('Impossibile contattare il server');
}
}
- chiamata al server con Ajax gestito da una Promise
// function che prende in input l'url e ritorna la promise
function getPromise(url) {
return new Promise(function(resolve, reject) {
let objhttp = new XMLHttpRequest();
objhttp.open('GET', url);
objhttp.send();
objhttp.onreadystatechange = function() {
if (objhttp.readyState == 4) {
if (objhttp.status == 200) {
resolve(objhttp.responseText);
} else {
reject("Impossibile contattare il server");
}
}
};
});
}
//funzione che viene chiamata per effettuare la chiamata al server con ajax
function callServer() {
let promise = getPromise('http://localhost/promiseAjax/film.php');
promise.then((risultato) => {
let objDati = JSON.parse(risultato);
let ul = '<ul>';
ul += objDati.map((film) => '<li>' + film.titolo + '</li>');
document.getElementById('content').innerHTML = ul;
}).catch((err) => console.log(err));
}
API FETCH
La Fetch API fornisce un'interfaccia per ottenere risorse (anche attraverso la rete). L’utilizzo di XMLHttpRequest per la
gestione di chiamate HTTP da JavaScript risulta abbastanza scomodo, per questo ed altri motivi, è stata definita una
alternativa a XMLHttpRequest: l’API fetch().
Rispetto a XMLHttpRequest, fetch() ha una sintassi più semplice e meglio integrata nel modello ad oggetti di
JavaScript. L’API prevede una gestione delle chiamate asincrone basata sulle promise, ritorna sempre una Promise,
ed è pensata per essere estesa ed utilizzabile in diversi contesti, non solo all’interno del browser. Ritornando una
Promise, si può gestire la sua risposta con il metodo then() o una funzione asincrona.
Vediamo passo passo l’ esempio precedente, della chiamata al server con Ajax gestito da una Promise, definito con
l’API fetch():
function fetchCall() {
let url = 'http://localhost/promiseAjax/film.php';
fetch(url).then(risposta => {
console.log(risposta)
}).catch(err => {
console.log(err)
})
. . .
}
Notiamo che abbiamo specificato l’URL su cui effettuare la richiesta HTTP come parametro della funzione fetch() e
gestito la risposta come una promise. In caso di successo la promise verrà risolta ed entreremo in then(), in cui ci
verrà fornita la risposta del server sotto forma di oggetto di tipo Response.
Response
body: ReadableStream { locked: false }
bodyUsed: false
headers: Headers { }
ok: true
redirected: false
status: 200
statusText: "OK"
type: "cors"
url: "http://localhost/promiseAjax/film.php"
<prototype>: ResponsePrototype { clone: clone(), arrayBuffer: arrayBuffer(), blob: blob(), … }
L’oggetto Response vediamo che ci fornisce proprietà e metodi che possono essere chiamati in modo semplice per
conoscerne i valori. Le proprietà più utilizzate sono:
- ok: È un valore booleano che indica se la risposta del server è stata positiva, cioè se il codice di stato
restituito è compreso tra 200 e 299.
- status: È un valore intero che indica il codice di stato HTTP inviato dal server, 200 in caso di risposta con
successo
- statusText: È una stringa associata al codice di stato, che ne descrive testualmente il significato.Se il codice di
risposta è 200, la stringa sarà "OK.
Occorre sottolineare che la promise restituita da fetch() viene risolta ogni qualvolta c’è una risposta da parte del
server, non solo quando otteniamo un codice di stato 200. In altre parole, se entriamo in then() non dobbiamo dare
per scontato di aver ottenuto il contenuto richiesto al server.
È buona norma verificare il codice di stato della risposta e gestirlo di conseguenza:
fetch('http://localhost/promiseAjax/film.php').then(risposta => {
if (risposta.ok) {
console.log("Contenuto ricevuto");
}
if (risposta.status >= 100 && risposta.status < 200) {
console.log("Informazioni per il client");
}
if (risposta.status >= 300 && risposta.status < 399) {
console.log("Redirezione");
}
if (risposta.status >= 400 && risposta.status < 499) {
console.log("Richiesta errata");
}
if (risposta.status >= 500 && risposta.status < 599) {
console.log("Errore sul server");
}
}).catch(error => console.log("Si è verificato un errore!"))
Notiamo che anche la condizione d’errore sul server (codici di stato compresi tra 500 e 599) determina la risoluzione
positiva della promise generata da fetch(). La promise viene rigettata solo quando si è verificato un problema
intrinseco nella comunicazione, come ad esempio nel caso in cui il server non risponde o non è disponibile una
connessione Internet, in questi casi si entra in catch().
Ciascuno di questi metodi restituisce una promise, quindi se ad esempio ci aspettiamo un JSON come risposta ad una
richiesta HTTP, potremo ottenere l’oggetto. L’esempio precedentedella chiamata al server con Ajax gestito da una
Promise, definito con l’API fetch() diventa:
function callServer() {
let url = 'http://localhost/promiseAjax/film.php';
fetch(url).then(risultato => {
if (risultato.ok) {
return risultato.json();
}
}).then(objFilm => {
let ul = '<ul>';
ul += objFilm.map((film) => '<li>' + film.titolo + '</li>');
document.getElementById('content').innerHTML = ul;
}).catch(error =>
console.log("Si è verificato un errore!")
)
}
Viene creato l’oggetto Request specificando l’URL a cui inviare la richiesta HTTP ed un oggetto di opzioni: method, gli
header ed il body.
La seconda alternativa a questa soluzione consiste nel passare le stesse impostazioni dell’oggetto Request
direttamente a fetch():
fetch(url , {
method: "post",
headers: new Headers({
"Content-Type": "application/json"
}),
body: JSON.stringify({
titolo: "Un articolo",
autore: "Mario Rossi"
})
}).then(...).catch(...)
Il contenuto del body di una richiesta dipende da come il server si aspetta i dati. Nell’esempio precedente, abbiamo
supposto che il server accettasse dati in formato JSON.
Se invece fosse previsto l’invio di dati tramite form, potremmo creare un oggetto FormData ed assegnarlo al body:
fetch(url , {
method: "post",
headers: new Headers({
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
}),
body: new FormData(document.getElementById("titolo"), document.getElementById("autore"))
}).then(...).catch(...)
Abbiamo supposto che il titolo e l’autore fossero contenuti nei omonimi campi di un form HTML.
Da notare anche il cambio di header che specifica il tipo di contenuto inviato tramite POST.
Oltre al metodo append(), la collezione degli header prevede alcuni metodi per la loro gestione:
- il metodo has(): verifica se nell’insieme è contenuto uno specifico header;
headers.has("Set-Cookie"); //false
headers.has("Content-Type"); //true
Dato che gli header determinano la comunicazione tra client e server, esistono delle limitazioni alla loro gestione,
determinate da una proprietà interna della collezione (guard) il cui valore, non gestibile da JavaScript, stabilisce quali
dei metodi visti possono essere utilizzati.
fetch(myRequest).then(res => {
if(res.ok){
if(res.headers.get('Content-Type').includes('application/json')){
console.log(res);
return res.json();
}
}
}).then(json =>{
promiseAll(json);
})
Quindi, nel then, il risultato della promise creata con fetch viene passato alla funzione promiseAll():
function promiseAll(list) {
var p = new Promise((resolve, reject) => {
setTimeout(()=> {
console.log('p è stato risolto');
resolve(list.map(ele => ele.name));
}, 5000)
});
}, 10000)
});
Se la prima promise fallisce, vedremo che la seconda promise sarà bloccata e si attiverà direttamente il catch di
promise.all. Il finally sarà richiamato sia quando la promise viene risolta che quando viene rigettata, mostrerà un
alert.
FUNCTION ASYNC/AWAIT
La coppia di parole chiave async/await consente la scrittura di codice asincrono pur mantenendo una struttura di
codice della programmazione sincrona. Non propongono un nuovo meccanismo di gestione di operazioni asincrone,
ma ne semplificano la sintassi. Infatti, async e await si basano sul meccanismo delle Promise. La parola chiave async
dichiara una funzione come asincrona, cioè che contiene un’operazione asincrona, mentre await sospende
l’esecuzione della funzione in attesa che la Promise associata ad un’attività asincrona venga risolta o rigettata.
Esempio:
function getUtente(userId) {
fetch("/utente/" + userId).then(response => {
console.log(response);
}).catch(error => console.log("Si è verificato un errore!"));
}
utilizza fetch() per effettuare una chiamata HTTP e quindi una operazione asincrona, e visualizzare sulla console i dati
di un utente oppure un messaggio d’errore.
Possiamo riscrivere la funzione utilizzando async e await in modo sincrono:
Vediamo che abbiamo definito la funzione getUtente() con async per indicare che all’interno di essa verrà eseguita
una operazione asincrona e notiamo che il codice nella funzione mantiene la struttura tipica di un normale codice
sincrono con il blocco try/catch per intercettare le eventuali eccezioni ed una chiamata a fetch() come se si trattasse
di una funzione sincrona. La presenza di await davanti all’invocazione di fetch(), fa in modo che l’esecuzione della
funzione getUtente() venga sospesa all’avvio dell’operazione asincrona e venga poi automaticamente ripresa
ottenuto un risultato, cioè quando la Promise associata a fetch() viene risolta o rigettata.
In poche parole la coppia async/await ci permette l’utilizzo della struttura sincrona del codice per gestire operazioni
asincrone e l’uso di try/catch per intercettare le eventuali eccezioni.
await può essere usata soltanto all’interno di funzioni async.
Vediamo un altro esempio, con l’uso di Promise.all() e prendiamo in considerazione la seguente funzione:
Essa carica i dati dell’utente, poi i dati del blog associato all’utente e infine le foto associate all’utente. Infine la
funzione ritorna un oggetto con tutte le informazioni recuperate.
Ciascuna operazione asincrona fetch() viene eseguita dopo il completamento della precedente invocazione. In
questo caso quindi, le operazioni asincrone non avvengono in parallelo. Se vogliamo migliorare le prestazioni
dell’applicazione e trarre beneficio dall’esecuzione parallela delle chiamate HTTP, utilizziamo il metodo Promise.all():