web-dev-qa-db-fra.com

Comment savoir si le chargement d'une balise <script> a échoué

J'ajoute de manière dynamique les balises <script> au <head> d'une page, et j'aimerais pouvoir savoir si le chargement a échoué - d'une manière 404, une erreur de script dans le script chargé, peu importe.

Dans Firefox, cela fonctionne:

var script_tag = document.createElement('script');
script_tag.setAttribute('type', 'text/javascript');
script_tag.setAttribute('src', 'http://fail.org/nonexistant.js');
script_tag.onerror = function() { alert("Loading failed!"); }
document.getElementsByTagName('head')[0].appendChild(script_tag);

Cependant, cela ne fonctionne pas dans IE ou Safari.

Est-ce que quelqu'un connaît un moyen de faire en sorte que cela fonctionne dans des navigateurs autres que Firefox?

(Je ne pense pas qu'une solution nécessitant de placer un code spécial dans les fichiers .js soit une bonne solution. Elle est peu élégante et inflexible.)

100
David

Il n'y a pas d'événement d'erreur pour la balise de script. Vous pouvez dire quand il réussit et supposer qu'il n'a pas été chargé après un délai d'attente:

<script type="text/javascript" onload="loaded=1" src="....js"></script>
31

Si vous ne vous souciez que des navigateurs html5, vous pouvez utiliser un événement d'erreur (puisqu'il ne s'agit que de la gestion des erreurs, il devrait être correct de ne le prendre en charge que sur les navigateurs de la prochaine génération pour KISS à mon humble avis).

De la spec:

Si la valeur de l'attribut src est le chaîne vide ou si cela ne peut pas être résolue, l'agent utilisateur doit alors mettre en file d'attente une tâche pour déclencher un événement simple nommé error à l'élément, et abandonner ces étapes.

~

Si le chargement a entraîné une erreur (par exemple, une erreur DNS ou une erreur HTTP 404 ), L'exécution du bloc de script doit obligatoirement consiste simplement à déclencher un événement simple erreur nommée à l'élément.

Cela signifie que vous n'avez pas à effectuer d'interrogation sujette aux erreurs et que vous pouvez le combiner avec un attribut async et différé pour vous assurer que le script ne bloque pas le rendu de la page:

L'attribut de report peut être spécifié même si l'attribut asynchrone est spécifié, pour provoquer hérité Web les navigateurs qui ne prennent en charge que différer (et pas async) se replient vers le différé comportement au lieu du synchrone comportement de blocage qui est le comportement par défaut.

Plus d'informations sur http://www.w3.org/TR/html5/scripting-1.html#script

21
andrerom

Le script d'Erwinus fonctionne très bien, mais le code n'est pas très clair. J'ai pris la liberté de le nettoyer et de déchiffrer ce qu'il faisait. J'ai apporté ces modifications:

  • Noms de variables significatifs
  • Utilisation de prototype.
  • require() utilise une variable d'argument
  • Aucun message alert() n'est renvoyé par défaut
  • Correction de quelques erreurs de syntaxe et problèmes de portée que je rencontrais

Merci encore à Erwinus, la fonctionnalité elle-même est parfaite.

function ScriptLoader() {
}

ScriptLoader.prototype = {

    timer: function (times, // number of times to try
                     delay, // delay per try
                     delayMore, // extra delay per try (additional to delay)
                     test, // called each try, timer stops if this returns true
                     failure, // called on failure
                     result // used internally, shouldn't be passed
            ) {
        var me = this;
        if (times == -1 || times > 0) {
            setTimeout(function () {
                result = (test()) ? 1 : 0;
                me.timer((result) ? 0 : (times > 0) ? --times : times, delay + ((delayMore) ? delayMore : 0), delayMore, test, failure, result);
            }, (result || delay < 0) ? 0.1 : delay);
        } else if (typeof failure == 'function') {
            setTimeout(failure, 1);
        }
    },

    addEvent: function (el, eventName, eventFunc) {
        if (typeof el != 'object') {
            return false;
        }

        if (el.addEventListener) {
            el.addEventListener(eventName, eventFunc, false);
            return true;
        }

        if (el.attachEvent) {
            el.attachEvent("on" + eventName, eventFunc);
            return true;
        }

        return false;
    },

    // add script to dom
    require: function (url, args) {
        var me = this;
        args = args || {};

        var scriptTag = document.createElement('script');
        var headTag = document.getElementsByTagName('head')[0];
        if (!headTag) {
            return false;
        }

        setTimeout(function () {
            var f = (typeof args.success == 'function') ? args.success : function () {
            };
            args.failure = (typeof args.failure == 'function') ? args.failure : function () {
            };
            var fail = function () {
                if (!scriptTag.__es) {
                    scriptTag.__es = true;
                    scriptTag.id = 'failed';
                    args.failure(scriptTag);
                }
            };
            scriptTag.onload = function () {
                scriptTag.id = 'loaded';
                f(scriptTag);
            };
            scriptTag.type = 'text/javascript';
            scriptTag.async = (typeof args.async == 'boolean') ? args.async : false;
            scriptTag.charset = 'utf-8';
            me.__es = false;
            me.addEvent(scriptTag, 'error', fail); // when supported
            // when error event is not supported fall back to timer
            me.timer(15, 1000, 0, function () {
                return (scriptTag.id == 'loaded');
            }, function () {
                if (scriptTag.id != 'loaded') {
                    fail();
                }
            });
            scriptTag.src = url;
            setTimeout(function () {
                try {
                    headTag.appendChild(scriptTag);
                } catch (e) {
                    fail();
                }
            }, 1);
        }, (typeof args.delay == 'number') ? args.delay : 1);
        return true;
    }
};

$(document).ready(function () {
    var loader = new ScriptLoader();
    loader.require('resources/templates.js', {
        async: true, success: function () {
            alert('loaded');
        }, failure: function () {
            alert('NOT loaded');
        }
    });
});
20
Aram Kocharyan

Je sais que c'est un vieux fil, mais j'ai une bonne solution à vous (je pense). Il est copié d'une classe de la mienne, qui gère tous les éléments AJAX.

Lorsque le script ne peut pas être chargé, il définit un gestionnaire d'erreurs, mais lorsque le gestionnaire d'erreurs n'est pas pris en charge, il retombe sur un minuteur qui recherche les erreurs pendant 15 secondes.

function jsLoader()
{
    var o = this;

    // simple unstopable repeat timer, when t=-1 means endless, when function f() returns true it can be stopped
    o.timer = function(t, i, d, f, fend, b)
    {
        if( t == -1 || t > 0 )
        {
            setTimeout(function() {
                b=(f()) ? 1 : 0;
                o.timer((b) ? 0 : (t>0) ? --t : t, i+((d) ? d : 0), d, f, fend,b );
            }, (b || i < 0) ? 0.1 : i);
        }
        else if(typeof fend == 'function')
        {
            setTimeout(fend, 1);
        }
    };

    o.addEvent = function(el, eventName, eventFunc)
    {
        if(typeof el != 'object')
        {
            return false;
        }

        if(el.addEventListener)
        {
            el.addEventListener (eventName, eventFunc, false);
            return true;
        }

        if(el.attachEvent)
        {
            el.attachEvent("on" + eventName, eventFunc);
            return true;
        }

        return false;
    };

    // add script to dom
    o.require = function(s, delay, baSync, fCallback, fErr)
    {
        var oo = document.createElement('script'),
        oHead = document.getElementsByTagName('head')[0];
        if(!oHead)
        {
            return false;
        }

        setTimeout( function() {
            var f = (typeof fCallback == 'function') ? fCallback : function(){};
            fErr = (typeof fErr == 'function') ? fErr : function(){
                alert('require: Cannot load resource -'+s);
            },
            fe = function(){
                if(!oo.__es)
                {
                    oo.__es = true;
                    oo.id = 'failed';
                    fErr(oo);
                }
            };
            oo.onload = function() {
                oo.id = 'loaded';
                f(oo);
            };
            oo.type = 'text/javascript';
            oo.async = (typeof baSync == 'boolean') ? baSync : false;
            oo.charset = 'utf-8';
            o.__es = false;
            o.addEvent( oo, 'error', fe ); // when supported

            // when error event is not supported fall back to timer
            o.timer(15, 1000, 0, function() {
                return (oo.id == 'loaded');
            }, function(){ 
                if(oo.id != 'loaded'){
                    fe();
                }
            });
            oo.src = s;
            setTimeout(function() {
                try{
                    oHead.appendChild(oo);
                }catch(e){
                    fe();
                }
            },1); 
        }, (typeof delay == 'number') ? delay : 1);  
        return true;
    };

}

$(document).ready( function()
{
    var ol = new jsLoader();
    ol.require('myscript.js', 800, true, function(){
        alert('loaded');
    }, function() {
        alert('NOT loaded');
    });
});
16
Codebeat

Pour vérifier si le javascript dans nonexistant.js n'a renvoyé aucune erreur, vous devez ajouter une variable dans http://fail.org/nonexistant.js comme var isExecuted = true;, puis vérifier si elle existe lors du chargement de la balise de script.

Cependant, si vous voulez seulement vérifier que le nonexistant.js est retourné sans un 404 (ce qui signifie qu'il existe), vous pouvez essayer avec une variable isLoaded ...

var isExecuted = false;
var isLoaded = false;
script_tag.onload = script_tag.onreadystatechange = function() {
    if(!this.readyState ||
        this.readyState == "loaded" || this.readyState == "complete") {
        // script successfully loaded
        isLoaded = true;

        if(isExecuted) // no error
    }
}

Cela couvrira les deux cas.

9
Luca Matteis

J'espère que cela ne sera pas réduit, car dans des circonstances spéciales, c'est le moyen le plus fiable de résoudre le problème. Chaque fois que le serveur vous permet d’obtenir une ressource Javascript en utilisant CORS ( http://en.wikipedia.org/wiki/Cross-Origin_resource_sharing ), vous disposez d’un large éventail d’options pour le faire.

L'utilisation de XMLHttpRequest pour récupérer des ressources fonctionnera sur tous les navigateurs modernes, y compris IE. Puisque vous souhaitez charger du Javascript, vous avez le Javascript à votre disposition en premier lieu. Vous pouvez suivre les progrès en utilisant readyState ( http://en.wikipedia.org/wiki/XMLHttpRequest#The_onreadystatechange_event_listener ). Enfin, une fois que vous avez reçu le contenu du fichier, vous pouvez l'exécuter avec eval (). Oui, j’ai dit eval - parce que la sécurité n’est pas différente du chargement normal du script. En fait, John Resig a suggéré de faire appel à une technique similaire ( http://ejohn.org/blog/degrading-script-tags/ ).

Cette méthode vous permet également de séparer le chargement de eval et d'exécuter des fonctions avant et après l'éval. Cela devient très utile lorsque vous chargez des scripts en parallèle mais que vous les évaluez les uns après les autres - les navigateurs peuvent le faire facilement lorsque vous placez les balises en HTML, mais ne vous laissez pas ajouter des scripts lors de l'exécution avec Javascript.

CORS est également préférable à JSONP pour le chargement de scripts ( http://en.wikipedia.org/wiki/XMLHttpRequest#Cross-domain_requests ). Toutefois, si vous développez vos propres widgets tiers pour les incorporer à d'autres sites, vous devez en fait charger les fichiers Javascript de votre propre domaine dans votre propre iframe (à nouveau, en utilisant AJAX).

En bref:

  1. Essayez de voir si vous pouvez charger la ressource en utilisant AJAX GET

  2. Utiliser eval après le chargement réussi

Pour l'améliorer:

  1. Vérifiez les en-têtes de contrôle de cache envoyés

  2. Recherchez par ailleurs la mise en cache du contenu dans localStorage, si vous devez

  3. Découvrez le "dégradant javascript" de Resig pour un code plus propre

  4. Découvrez require.js

6
Gregory Magarshak

Cette astuce a fonctionné pour moi, bien que j'avoue que ce n'est probablement pas la meilleure façon de résoudre ce problème. Au lieu d'essayer cela, vous devriez voir pourquoi les javascripts ne se chargent pas. Essayez de conserver une copie locale du script sur votre serveur, etc. ou contactez le fournisseur tiers à partir duquel vous essayez de télécharger le script.

Quoi qu'il en soit, voici la solution de contournement: 1) Initialisez une variable à false 2) Définissez-la sur true lors du chargement du javascript (à l'aide de l'attribut onload) 3) vérifiez si la variable est vraie ou fausse une fois. le corps HTML a chargé

<html>
  <head>
    <script>
      var scriptLoaded = false;

      function checkScriptLoaded() {
        if (scriptLoaded) {
          // do something here
        } else {
          // do something else here!
        }
      }
    </script>
    <script src="http://some-external-script.js" onload="scriptLoaded=true;" />
  </head>
  <body onload="checkScriptLoaded()">
    <p>My Test Page!</p>
  </body>
</html>
5
Gautam Tandon

Voici une autre solution basée sur JQuery sans aucun minuteur:

<script type="text/javascript">
function loadScript(url, onsuccess, onerror) {
$.get(url)
    .done(function() {
        // File/url exists
        console.log("JS Loader: file exists, executing $.getScript "+url)
        $.getScript(url, function() {
            if (onsuccess) {
                console.log("JS Loader: Ok, loaded. Calling onsuccess() for " + url);
                onsuccess();
                console.log("JS Loader: done with onsuccess() for " + url);
            } else {
                console.log("JS Loader: Ok, loaded, no onsuccess() callback " + url)
            }
        });
    }).fail(function() {
            // File/url does not exist
            if (onerror) {
                console.error("JS Loader: probably 404 not found. Not calling $.getScript. Calling onerror() for " + url);
                onerror();
                console.error("JS Loader: done with onerror() for " + url);
            } else {
                console.error("JS Loader: probably 404 not found. Not calling $.getScript. No onerror() callback " + url);
            }
    });
}
</script>

Merci à: https://stackoverflow.com/a/14691735/1243926

Exemple d'utilisation (exemple original tiré de documentation JQuery getScript ):

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>jQuery.getScript demo</title>
  <style>
  .block {
     background-color: blue;
     width: 150px;
     height: 70px;
     margin: 10px;
  }
  </style>
  <script src="http://code.jquery.com/jquery-1.9.1.js"></script>
</head>
<body>

<button id="go">&raquo; Run</button>
<div class="block"></div>

<script>


function loadScript(url, onsuccess, onerror) {
$.get(url)
    .done(function() {
        // File/url exists
        console.log("JS Loader: file exists, executing $.getScript "+url)
        $.getScript(url, function() {
            if (onsuccess) {
                console.log("JS Loader: Ok, loaded. Calling onsuccess() for " + url);
                onsuccess();
                console.log("JS Loader: done with onsuccess() for " + url);
            } else {
                console.log("JS Loader: Ok, loaded, no onsuccess() callback " + url)
            }
        });
    }).fail(function() {
            // File/url does not exist
            if (onerror) {
                console.error("JS Loader: probably 404 not found. Not calling $.getScript. Calling onerror() for " + url);
                onerror();
                console.error("JS Loader: done with onerror() for " + url);
            } else {
                console.error("JS Loader: probably 404 not found. Not calling $.getScript. No onerror() callback " + url);
            }
    });
}


loadScript("https://raw.github.com/jquery/jquery-color/master/jquery.color.js", function() {
  console.log("loaded jquery-color");
  $( "#go" ).click(function() {
    $( ".block" )
      .animate({
        backgroundColor: "rgb(255, 180, 180)"
      }, 1000 )
      .delay( 500 )
      .animate({
        backgroundColor: "olive"
      }, 1000 )
      .delay( 500 )
      .animate({
        backgroundColor: "#00f"
      }, 1000 );
  });
}, function() { console.error("Cannot load jquery-color"); });


</script>
</body>
</html>
5
osa

ma solution propre de travail (2017)

function loaderScript(scriptUrl){
   return new Promise(function (res, rej) {
    let script = document.createElement('script');
    script.src = scriptUrl;
    script.type = 'text/javascript';
    script.onError = rej;
    script.async = true;
    script.onload = res;
    script.addEventListener('error',rej);
    script.addEventListener('load',res);
    document.head.appendChild(script);
 })

}
4
pery mimon

Eh bien, la seule façon dont je peux penser à faire tout ce que tu veux est assez moche. Effectuez d’abord un appel AJAX pour récupérer le contenu du fichier Javascript. Lorsque cela est terminé, vous pouvez vérifier le code de statut pour décider si cela a réussi ou non. Ensuite, prenez le responseText de l'objet xhr et enroulez-le dans un try/catch, créez dynamiquement une balise de script et, pour IE, vous pouvez définir la propriété text de la balise script sur le texte JS, navigateurs, vous devriez pouvoir ajouter un nœud de texte avec le contenu de la balise script. Si un code s'attend à ce qu'une balise de script contienne réellement l'emplacement src du fichier, cela ne fonctionnera pas, mais cela devrait fonctionner dans la plupart des situations. Moche, mais travaillera. Vous pouvez consulter mon exemple rapide et sale ici: http://pastie.org/829775

2
nickjsify

Cela peut être fait en toute sécurité en utilisant des promesses

function loadScript(src) {
  return new Promise(function(resolve, reject) {
    let script = document.createElement('script');
    script.src = src;

    script.onload = () => resolve(script);
    script.onerror = () => reject(new Error("Script load error: " + src));

    document.head.append(script);
  });
}

et utiliser comme ça

let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js");

promise.then(
  script => alert(`${script.src} is loaded!`),
  error => alert(`Error: ${error.message}`)
);
1
Nithin Chandran

Cela ne fonctionne pas dans Safari parce que vous utilisez la syntaxe d'attribut. Cela fonctionnera bien si:

script_tag.addEventListener('error', function(){/*...*/}, true);

... sauf dans IE.

Si vous voulez vérifier que le script a été exécuté avec succès, définissez simplement une variable à l'aide de ce script et vérifiez qu'elle est définie dans le code externe.

1
user42092

Voici comment j'ai utilisé une promesse pour détecter les erreurs de chargement qui sont émises sur l'objet window:

<script type='module'>
window.addEventListener('error', function(error) {
  let url = error.filename
  url = url.substring(0, (url.indexOf("#") == -1) ? url.length : url.indexOf("#"));
  url = url.substring(0, (url.indexOf("?") == -1) ? url.length : url.indexOf("?"));
  url = url.substring(url.lastIndexOf("/") + 1, url.length);
  window.scriptLoadReject && window.scriptLoadReject[url] && window.scriptLoadReject[url](error);
}, true);
window.boot=function boot() {
  const t=document.createElement('script');
  t.id='index.mjs';
  t.type='module';
  new Promise((resolve, reject) => {
    window.scriptLoadReject = window.scriptLoadReject || {};
    window.scriptLoadReject[t.id] = reject;
    t.addEventListener('error', reject);
    t.addEventListener('load', resolve); // Careful load is sometimes called even if errors prevent your script from running! This promise is only meant to catch errors while loading the file.
  }).catch((value) => {
    document.body.innerHTML='Error loading ' + t.id + '! Please reload this webpage.<br/>If this error persists, please try again later.<div><br/>' + t.id + ':' + value.lineno + ':' + value.colno + '<br/>' + (value && value.message);
  });
  t.src='./index.mjs'+'?'+new Date().getTime();
  document.head.appendChild(t);
};
</script>
<script nomodule>document.body.innerHTML='This website needs ES6 Modules!<br/>Please enable ES6 Modules and then reload this webpage.';</script>
</head>

<body onload="boot()" style="margin: 0;border: 0;padding: 0;text-align: center;">
  <noscript>This website needs JavaScript!<br/>Please enable JavaScript and then reload this webpage.</noscript>
0
Mi G

vous pouvez inclure une fonction dans le fichier de script Java si possible comme check () {var report; report = True}, puis exécutez cette fonction si la fonction ne s'exécute pas ou ne répond pas, le script n'est pas chargé sinon, je pense le script n'est pas prêt à être utilisé. L'attribut onload est également utile. 

0
Abhishek Mathur

Il a été proposé de définir un délai d'attente, puis d'assumer une défaillance de chargement après un délai d'attente.

setTimeout(fireCustomOnerror, 4000);

Le problème avec cette approche est que l'hypothèse est basée sur le hasard. Une fois votre délai expiré, la demande est toujours en attente. La demande du script en attente peut se charger même après que le programmeur a supposé que la charge ne se produirait pas.

Si la demande pouvait être annulée, le programme pourrait attendre une période, puis annuler la demande. 

0
Garrett

Evénement onerror

* Mise à jour août 2017: onerror est renvoyé par Chrome et Firefox. onload est déclenché par Internet Explorer. Edge ne déclenche ni erreur ni charge. Je n'utiliserais pas cette méthode, mais cela pourrait fonctionner dans certains cas. Voir également

<link> onerror ne fonctionne pas dans IE

*

Définition et utilisation L'événement onerror est déclenché si une erreur se produit lors du chargement d'un fichier externe (par exemple, un document ou une image).

Astuce: Lorsqu'ils sont utilisés sur un support audio/vidéo, les événements connexes qui se produisent lorsqu'il y a une quelconque perturbation du processus de chargement du support sont les suivants:

  • un jour
  • négligé
  • installé
  • suspendre

En HTML:

élément onerror = "myScript">

En JavaScript , à l'aide de la méthode addEventListener ():

object.addEventListener ("error", myScript);

Remarque: La méthode addEventListener () n'est pas prise en charge dans Internet Explorer 8 et les versions antérieures.

Exemple Exécute un JavaScript si une erreur se produit lors du chargement d'une image:

img src = "image.gif" onerror = "myFunction ()">

0
William Monahan