Sei sulla pagina 1di 16

JAVASCRIPT ES6

JAVASCRIPT ECMA SCRIPT 6 (ES6)


Un ripasso delle novità introdotte da Javascript ES6. Queste sono:
 nuovi tipo di variabili: let e const
 un nuovo metodo per scrivere le stringhe letterali (literal string)
 un nuovo modo per scrivere le funzioni: arrow function
 parametro di default e operatore rest
 scomporre gli elementi di un array: operatore spread
 usare le classi
 nuovi metodi per: le stringhe (startsWith(), endsWith(), includes()), per gli array (from())
 nuovo tipo di ciclo: ciclo for..of

VARIABILI LET E CONST


Sappiamo che in Javascript normalmente le variabili si definiscono con var, ma questa definizione ha una
caratteristica che può provocare bug nel codice, ovvero una variabile definita con var è una variabile locale solo
all’interno delle function, ma se definita all’interno di altri blocchi { ... } (if, for, while,...) rimane una variabile globale.
Quindi per rendere il codice più sicuro sono state introdotte le variabili di blocco: let e const.

Una variabile definita con let:


let x = 3;
è una variabile di blocco, perchè se definita all’interno di un qualsiasi blocco, è visibile solo in quel blocco e non al di
fuori di esso, dove darebbe un errore in quanto risulterebbe non definita (undefined).
È consigliato, facendo molta attenzione, definire quante più variabili let possibile per rendere il codice più sicuro.
{ {
var x = 3; let x = 3;
} }
document.write(x);  3 document.write(x); error: x no defined

Una variabile definita con const diventa una costante:


const x = 3;
Come let è una variabile di blocco con la differenza che una costante non può essere reinizializzata, se
successivamante si scrive :
x = 6;
questo darà un errore. Però, essendo una variabile di blocco è possibile ridefinirla in un blocco:
const x = 3;
{ questo non darà un errore in quanto il const nel blocco
const x = 6; esiste solo per quel blocco. Il codice darà in output 6 e 3.
document.write(x); Ma se nel blocco, la variabile si definisce con var, questo
} darà errore.
document.write(x);
Se si dichiara con const un array o un oggetto, questo non può essere reinizializzato, ma è possibile modificare gli
elementi presenti o oggiungere nuovi elementi. Se abbiamo:
const frutta = [‘banana’, ‘mela’, ‘pera’];
frutta = [‘kiwi’, ‘ananas’];  errore
frutta[1] = ‘kiwi’;  banana, kiwi, pera
frutta.push(‘ananas’);  banana, mela, pera, ananas

NUOVO METODO PER LE STRINGHE LITERAL


Javascript ES6 introduce un nuovo metodo per dichiarare le stringhe letterali, molto utile soprattutto per stringhe
molto lunghe, per evitare di contatenarle e, per la loro formattazione.
Al posto dei normali apici ‘ ’ e virgolette “ ”, si usa il back tick ` `, simili ad apici inversi (ALT + 0096):
let stringa = `Ciao a tutti`;
Questa sintassi consente di andare a capo e per quanto riguarda la formattazione consente di inserire direttamante
tag html:
let stringa = `Ciao a <br>tutti`;
Oltre a questo un’altra importante caratteristica è che consente di inserire ed eseguire al suo interno codice
Javascript, che deve essere inserito in : ${ ...... }:

let nome = 'Niko';


let stringa = `Ciao a tutti mi chiamo ${nome}`;
document.write(stringa); Ciao a tutti mi chiamo Niko

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:

function somma(x, y = 5) { //5 parametro di default di y


return x + y;
}
document.write(somma(2, 14));  16 //viene passato 14 che sovrascrive 5 in y
document.write(somma(2));  7 //non viene passato parametro y usa quello 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:

function somma(j, ...n) { ... }

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.

 Concatenazione o combinazione di array


Consiste nell’inserire un array all’interno di un altro array, normalmente:
var arr1 = [3, 4];
var arr2 = [1, 2, arr1, 5, 6];
questo esempio genera:
[1, 2, [3, 4], 5, 6]
otteniamo un array all’interno di un array.
Questo non darebbe un errore, ma si usa l’operatore spread per ottenere un array singolo con valori di arr1 e arr2:
var arr1 = [3, 4];
var arr2 = [1, 2, ...arr1, 5, 6];
Il risultato sarà:
[1, 2, 3, 4, 5, 6]
arr1 si espande in arr2, ovvero ogni singolo elemento di arr1 viene inserito in arr2.

 Utilizzo di un array come parametri di una funzione


Questo è l’utilizzo più importante dell’operatore spread, le funzioni normalmente si aspettano un elenco di parametri
e non un array, con esso darebbero come risultato NaN:
let numeri = [37, -10, 7, 1];
Math.max(numeri);  NaN
Math.max(...numeri);  37

 Trasformare una stringa in un array


var str = "hello";
var chars = [...str];
console.log(chars);
Output: ["h", "e", "l", "l", "o"]
LE CLASSI IN JAVASCRIPT
Fino a Javascript ES5 non esisteva il concetto di classe, per creare un concetto generico dalla quale poi istanziare
oggetti, si creava una function costruttore invocata ogni volta in cui si voleva istanziare un oggetto da essa.
Da Javascript ES6 è stato introdotto il costrutto delle classi, ma per javascript sono concettualmente diverse dalle
classi trovate in altri linguaggi di programmazione, in quanto sono solo un modo diverso per scrivere una funzione
costruttore. Il loro pregio è di semplificare la sintassi per creare un costruttore, senza aggiungere altro.
Prima di utilizzare una classe per istanziare oggetti, bisogna definirla. La sintassi è:
class nome {
constructor(val1, val2,....) {
proprietà1 = val1;
proprieta2 = val2;
...
}
metodi() { . . . }
}
Notiamo che si definiscono nel blocco construction() le proprietà della classe, mentre i metodi vengono definiti al di
fuori di esso.

Esempio con function costruttore:


function Auto(marca, colore) {
this.marca = marca;
this.colore = colore;
this.descrizione = function() {
alert(“Auto: “ + this.marca + “ di colore: “ + this.colore);
}
}
var polo = new Auto(‘Voltwagen’, ‘nero’);

Esempio con classe:


class Auto {
constructor(marca, colore) {
this.marca = marca;
this.colore = colore;
}
descrizione() {
alert(“Auto: “ + this.marca + “ di colore: “ + this.colore);
}
}
var polo = new Auto(‘Voltwagen’, ‘nero’);

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.

Esempio: ciclare un array


var cars = ['BMW', 'Volvo', 'Mini'];
var x;
for (x of cars) {
  document.write(x + "<br >");
}

Esempio: ciclare una stringa


var txt = 'JavaScript';
var x;
for (x of txt) {
  document.write(x + "<br >");
}

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();
}

if (callback && typeof callback === 'function') callback();


è una funzione di controllo che verifica per prima cosa la presenza del parametro "callback", poi verifica che si tratti
di una funzione, utilizzando il comando typeof e verificando che il valore sia function,. Se la condizione passa, viene
chiamata la funzione.

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:

var promise = new Promise(function(resolve, reject) {


if (condizione) {
resolve(valore);
} else {
reject(motivo);
}
});

Il costruttore Promise() prevede un parametro rappresentato da una funzione che ha il compito di gestire la


risoluzione o il rigetto della promise stessa.
Il primo parametro resolve rappresenta la funzione da invocare quando il valore restituito dall’operazione asincrona
è disponibile. Il valore restituito dalla attività asincrona viene passato a tale funzione.
Il secondo parametro reject è la funzione da invocare quando la promise non può essere risolta, ad esempio perchè
si è verificato un errore o perché il valore restituito non è considerato valido. In questo caso alla funzione viene
passato il motivo del rigetto, come può essere ad esempio l’eccezione che si è verificata.

Stati di una Promise


Una Promise può trovarsi in 3 stati:
- Pending: quando non è né risolta né rigettata, cioè la richiesta di esecuzione di un’attività asincrona è partita
ma non abbiamo ancora ricevuto un risultato
- Fullfilled: quando è risolta, il valore che rappresenta diviene disponibile, cioè quando l’attività asincrona
restituisce un valore
- Rejected: quando è rigettata, l’attività asincrona associata non restituisce un valore o perché si è verificata
un’eccezione oppure perché il valore restituito non è considerato valido.

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”.

AJAX CON PROMISE


Vediamo adesso un esempio concreto di come trasformare e gestire una chiamata Ajax al server con le Promise in
modo che risulti più semplice e leggibile.
Creiamo un file html per visualizzare i dati, un file php per i dati sul server e un file ajax.js per la chiamata Ajax al
server.

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;
?>

Che ritorna il seguente JSON:


0
titolo: "The Wolf of Wall Street"
"Leonardo Di Caprio"
attore:
anno: 2013
1
titolo : "Interstellar"
attore : "Matthew McConaughey"
anno: 2014
2
titolo: "Rocky"
attore : "Sylvester Stallone"
anno: 1976
Index.html
File html che visualizza i dati ricevuti dal server

<!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 normale chiamata al server con Ajax

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().

Interpretare una risposta HTTP


Una volta inviata la richiesta al server, vorremo leggere il contenuto della risposta. L’oggetto Response ci mette a
disposizione alcuni metodi per ottenere il contenuto restituito dal server in base al tipo:
- text(): ritorna il contenuto sottoforma di testo.
- json(): effettua il parsing del contenuto e lo ritorna sottoforma di oggetto.
- blob(): ritorna il contenuto sottoforma di dati non strutturati (blob).
- arrayBuffer():

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!")
    )
}

Invio di dati al server


L’esempio di richiesta HTTP visto fin qui rappresenta la forma più semplice di utilizzo di fetch(). Abbiamo specificato
soltanto l’URL verso cui intendiamo indirizzare la nostra richiesta. In questo caso è sottinteso l’uso di GET.
Se intendiamo specificare altri verbi HTTP abbiamo due possibilità.
La prima consiste nel creare un oggetto di tipo Request e passarlo alla funzione fetch(), come mostrato di seguito:

var richiesta = new Request(url , {


method: "post",
headers: new Headers({
"Content-Type": "application/json"
}),
body: JSON.stringify({
titolo: "Un articolo",
autore: "Mario Rossi"
})
});
fetch(richiesta).then(...).catch(...)

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.

Gestire gli header


Nell’ invio di dati al server tramite POST, bisogna specificare l’header Content-Type per informare il server sul
formato dei dati che stiamo inviando. Per la creazione degli header si fa ricorso al costruttore Headers(). Possiamo
creare una collezione di header passando al costruttore un oggetto che mappa i nomi degli header ai rispettivi valori:

var headers = new Headers({


"Content-Type": "application/json",
"Accept": "application/json",
"X-Custom": "Valore per headers custom"
});

In alternativa possiamo aggiungere dinamicamente gli header ad una collezione vuota:


var headers = new Headers();
headers.append("Content-Type", "application/json");
headers.append("Accept", "application/json");
headers.append("X-Custom", "Valore per headers custom");

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

- metodo set(): cambia il valore di un header;


headers.set("Content-Type", "text/plain");

- metodo get(): ottiene il valore di uno specifico


console.log(headers.get("Accept")); //application/json

- metodo delete(): elimina un header


headers.delete("X-Custom");

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.

METODO ALL() DI PROMISE


Il metodo Promise.all(iterabile) ritorna una singola promise che viene risolta quando tutte le promise nell'iterable
passate come parametro vengono risolte. Ci consente di eseguire diverse promise in parallelo e avere tutti i dati
alla conclusione dell’ultima promise, non importa quale sarà risolta per prima, verranno processate tutte insieme
una volta terminate tutte. Questo si fa passando come parametro un array di promise, se una promise dell’array
viene rigettata blocca tutte le altre promise in esecuzione.
Vediamo un esempio: passiamo i risultati del fetch ad un’altra funzione che andrà a creare due promise che
richiederanno una 5 secondi, l’altra 10, dovremmo svolgere delle operazioni quando entrambe queste promise
saranno terminate, ma nello stesso tempo non vogliamo far partire la seconda promise dopo che ha finito la prima,
ma contemporaneamente, in modo che tutto sia più veloce. Nessuna delle due promise dipende dall’altra.
Iniziamo quindi a richiamare una funzione al termine del nostro fetch risolto:

let headers = new Headers();


headers.append('Authorization', 'Bearer fdskjfbsdfb');
let init = {
headers: headers,
method: 'GET',
};
fetch('http://localhost:8080/index.php', init).then(res => {
if(res.ok){
if(res.headers.get('Content-Type').includes('application/json')){
console.log(res);
return res.json();
}
}
}).then(json => {
promiseAll(json);
}).catch(err => {
console.log('Errore di connessione');
});

Possiamo anche creare prima una Request e poi passarla al fetch:

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)
});

var p2 = new Promise((resolve, reject) => {


setTimeout(()=> {
console.log('p2 è stato risolto');
resolve(list.map(ele => ele.surname));
}, 10000)
});

Promise.all([p, p2]).then(res => {


document.getElementsByTagName('body')[0].innerHTML = JSON.stringify(res);
})
}
Questa chiamata torna un elenco di persone con nome e cognome, supponiamo quindi che dopo una serie di
operazioni, con chiamate al server, ci ritorni un json. Tramite il metodo map andiamo a far tornare un array con i
nomi, nella seconda promise un array con i cognomi. La prima promise ha un timeout di 5 secondi, quindi verrà
risolta dopo 5 secondi, la seconda invece 10, il console.log ci consente di vedere quando termina la prima promise e
constatare che per dare il risultato aspetta la seconda. Se andiamo ad eseguire vedremo che dopo 5 secondi verrà
risolta la prima e avremo in console il messaggio ‘p è stato risolto‘, a questo punto si attenderà la risoluzione della
seconda promise, che nel frattempo è già in esecuzione, son già passati i primi 5 secondi, ne dovremmo aspettare
altri 5, terminata la seconda promise sarà eseguito il then dopo la promise.all.
A questo punto res conterrà un array con le due response, in questo caso avremo un array che conterrà un array
con i nomi e un array con i cognomi:
[["Massimiliano","Mario","Luca"],["Salerno","Rossi","Bianchi"]]
Mettiamo un controllo per evitare che list sia vuoto e in quel caso rigettare la promise e tramite catch tornare un
messaggio:
function promiseAll(list) {
var p = new Promise((resolve, reject) => {
setTimeout(()=> {
if(list && list.length){
resolve(list.map(ele => ele.name));
} else {
reject({message: "La lista di nomi è vuota"});
}
}, 5000)
});

var p2 = new Promise((resolve, reject) => {


setTimeout(()=> {
if(list && list.length){
resolve(list.map(ele => ele.surname));
} else {
reject({message: "La lista di cognomi è vuota"});
}

}, 10000)
});

Promise.all([p, p2]).then(res => {


document.getElementsByTagName('body')[0].innerHTML = JSON.stringify(res);
}).catch(err => {
document.getElementsByTagName('body')[0].innerHTML = JSON.stringify(err);
}).finally(() => {
alert('Terminata la promise');
});
}

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:

async function getUtente(userId) {


try {
let response = await fetch("/utente/" + userId);
console.log(response);
} catch (e) {
console.log("Si è verificato un errore!");
}
}

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:

async function getBlogAndPhoto(userId) {


try {
let utente = await fetch("/utente/" + userId);
let blog = await fetch("/blog/" + utente.blogId);
let foto = await fetch("/photo/" + utente.albumId);
return {
utente,
blog,
foto
};
} catch (e) {
console.log("Si è verificato un errore!");
}
}

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():

async function getBlogAndPhoto(userId) {


try {
let utente = await fetch("/utente/" + userId);
let result = await Promise.all([
fetch("/blog/" + utente.blogId),
fetch("/photo/" + utente.albumId)
]);
return {
utente,
blog: result[0],
foto: result[1]
};
} catch (e) {
console.log("Si è verificato un errore!")
}
}
In questo caso attendiamo il completamento del caricamento dei dati dell’utente, essenziale per recuperare le altre
informazioni, e quindi rimaniamo in attesa del caricamento in parallelo dei dati del blog e delle foto.

Potrebbero piacerti anche

  • REACT
    REACT
    Documento29 pagine
    REACT
    bho
    Nessuna valutazione finora
  • Javascript
    Javascript
    Documento38 pagine
    Javascript
    bho
    Nessuna valutazione finora
  • Ajax
    Ajax
    Documento7 pagine
    Ajax
    bho
    Nessuna valutazione finora
  • HTML
    HTML
    Documento17 pagine
    HTML
    bho
    Nessuna valutazione finora