Il browser permette di tracciare il caricamento di risorse esterne: script, iframe, immagini e così via.
Esistono 2 eventi per tracciare il caricamento:
onload
– caricato con successo,onerror
– si è verificato un errore.
Caricamento di uno script
Diciamo che abbiamo necessità di caricare uno script di terze parti e chiamare una funzione che appartiene a questo script.
Possiamo caricarlo dinamicamente, in questo modo:
let script = document.createElement('script');
script.src = "my.js";
document.head.append(script);
…Ma come possiamo eseguire la funzione dichiarata all’interno di quello script? Dobbiamo attendere la fine del caricamento dello script e successivamente chiamare la funzione.
Per i nostri script dovremmo utilizzare i moduli JavaScript in questo caso, ma non sono largamente adottati dalle librerie di terze parti.
script.onload
Il principale helper è l’evento load
. Si innesca dopo che lo script è stato caricato ed eseguito.
Per esempio:
let script = document.createElement('script');
// si può caricare qualunque script, da qualunque dominio
script.src = "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"
document.head.append(script);
script.onload = function() {
// lo script crea una funzione helper "_"
alert(_); // la funzione è disponibile
};
Quindi nell’evento onload
possiamo utilizzare le variabili dello script, eseguire funzioni, ecc.
script.onerror
Gli errori che si verificano durante il caricamento dello script possono essere tracciati tramite l’evento error
.
! script.onerror = function() { alert("Caricamento fallito " + this.src); // Error loading https://example.com/404.js }; /!
Notate bene che in questo punto non possiamo ottenere i dettagli dell'errore HTTP. Non sappiamo se è un errore 404 o 500 o qualcos'altro.
```warn
Gli eventi `onload`/`onerror` tracciano solo il caricamento stesso.
Gli eventi load
e error
funzionano anche per le altre risorse, praticamente per qualunque risorsa che ha un src
esterno.
Per esempio:
let img = document.createElement('img');
img.src = "https://js.cx/clipart/train.gif"; // (*)
img.onload = function() {
alert(`Immagine caricata, dimensione ${img.width}x${img.height}`);
};
img.onerror = function() {
alert("Si è verificato un errore durante il caricamento dell'immagine");
};
Ci sono alcune note però:
- Per gli
<iframe>
, l’eventoiframe.onload
si aziona quando il caricamento dell’ iframe è terminato, sia in caso di successo che in caso di errore.
C’è una regola: gli script di un sito non possono accedere ai contenuti di un altro sito. Quindi, per esempio, uno script di https://facebook.com
non può leggere la casella di posta dell’utente di https://gmail.com
.
Per essere più precisi, un’origine (tripletta dominio/porta/protocollo) non può accedere al contenuto di un’altra. Quindi se abbiamo un sottodominio, o anche solo un’altra porta, questo sarà un’origine differente e quindi non hanno accesso l’uno con l’altro.
Questa regola interessa anche le risorse di altri domini.
Se stiamo utilizzando uno script di un altro dominio e c’è un errore, non possiamo ottenere i dettagli di quell’errore.
Per esempio, prendiamo lo script error.js
, che consiste in una singola chiamata ad una funzione (sbagliata):
// 📁 error.js
noSuchFunction();
Ora caricatela dallo stesso sito su cui è situato lo script:
<script>
window.onerror = function(message, url, line, col, errorObj) {
alert(`${message}\n${url}, ${line}:${col}`);
};
</script>
<script src="/article/onload-onerror/crossorigin/error.js"></script>
Vedremo il report dell’errore, come questo:
Uncaught ReferenceError: noSuchFunction is not defined
https://javascript.info/article/onload-onerror/crossorigin/error.js, 1:1
Ora carichiamo lo stesso script da un altro dominio:
<script>
window.onerror = function(message, url, line, col, errorObj) {
alert(`${message}\n${url}, ${line}:${col}`);
};
</script>
<script src="https://cors.javascript.info/article/onload-onerror/crossorigin/error.js"></script>
Il report di errore è diverso rispetto a quello precedente, come questo:
Script error.
, 0:0
Ci sono molti servizi (e possiamo anche sviluppare il nostro) che stanno in ascolto sugli errori globali, utilizzando window.onerror
, salvano gli errori e forniscono un interfaccia per accedere ed analizzarli. Fantastico, possiamo vedere i veri errori, scaturiti dai nostri utenti. Ma se uno script è caricato da un altro dominio non avremo nessuna informazioni sull’errore, come abbiamo appena visto.
Una policy cross-origin (CORS) simile viene applicata anche per altri tipi di risorse.
Per consentire l’accesso cross-origin il tag <script>
deve avere l’attributo crossorigin
e il server remoto deve fornire degli header speciali.
Ci sono tre livelli di accesso cross-origin:
- Attributo
crossorigin
non presente – accesso vietato. crossorigin="anonymous"
– accesso consentito se il server risponde con l’headerAccess-Control-Allow-Origin
con il valore*
o il nome della nostra origin (dominio). Il browser non manda dati e cookie sull’autenticazione al server remoto.crossorigin="use-credentials"
– accesso consentito se il server manda indietro l’headerAccess-Control-Allow-Origin
con la nostra origine (dominio) eAccess-Control-Allow-Credentials: true
. Il browser manda i dati e i cookie sull’autenticazione al server remoto.
Puoi approfondire l’accesso cross-origin nel capitolo Fetch: Cross-Origin Requests. Descrive il metodo fetch
per le richieste di rete, ma la policy è esattamente la stessa.
Ad esempio i “cookies” sono un argomento fuori dal nostro attuale ambito, ma puoi leggere informazioni a proposito nel capitolo Cookies, document.cookie.
Nel nostro caso non avevamo nessun attributo crossorigin, quindi l’accesso era vietato. Aggiungiamo l’attributo ora.
Possiamo scegliere tra "anonymous"
(non vengono mandati cookie, è necessario un header lato server) e "use-credentials"
(manda i cookie, sono necessari 2 header lato server).
Se non ci interessano i cookie allora "anonymous"
è la scelta giusta:
<script>
window.onerror = function(message, url, line, col, errorObj) {
alert(`${message}\n${url}, ${line}:${col}`);
};
</script>
<script crossorigin="anonymous" src="https://cors.javascript.info/article/onload-onerror/crossorigin/error.js"></script>
Ora, supponendo che il server fornisca l’header Access-Control-Allow-Origin
, riusciamo ad avere il report completo dell’errore.
Riepilogo
Immagini <img>
, fogli di stile esterni, script e altre risorse forniscono gli eventi load
e error
per tracciare i loro caricamento:
load
si aziona se il caricamento va a buon fine,error
si azione se si verifica un errore durante il caricamento.
L’unica eccezione è <iframe>
: per ragioni storiche scatta sempre l’evento load
, per qualunque esito del caricamento, anche se la pagina non è stata trovata.
Possiamo monitorare il caricamento delle risorse anche tramite l’evento readystatechange
, ma è poco utilizzato, perché gli eventi load/error
sono più semplici.