web-dev-qa-db-fra.com

document.createElement ("script") de manière synchrone

Est-il possible d'appeler un .js file file de façon synchrone et l’utilise immédiatement après?

<script type="text/javascript">
    var head = document.getElementsByTagName('head').item(0);
    var script = document.createElement('script');
    script.setAttribute('type', 'text/javascript');
    script.setAttribute('src', 'http://mysite/my.js');
    head.appendChild(script);

    myFunction(); // Fails because it hasn't loaded from my.js yet.

    window.onload = function() {
        // Works most of the time but not all of the time.
        // Especially if my.js injects another script that contains myFunction().
        myFunction();
    };
</script>

Ceci est simplifié. Dans mon implémentation, les éléments createElement sont dans une fonction. J'ai pensé à ajouter quelque chose à la fonction qui pourrait vérifier si une certaine variable était instanciée avant de rendre le contrôle. Mais ensuite, il reste le problème de savoir quoi faire lors de l’inclusion de js depuis un autre site sur lequel je n’ai aucun contrôle.

Pensées?

Modifier:

J'ai accepté la meilleure réponse pour le moment parce que cela explique bien ce qui se passe. Mais si quelqu'un a des suggestions pour améliorer cela, je suis ouvert à lui. Voici un exemple de ce que j'aimerais faire.

// Include() is a custom function to import js.
Include('my1.js');
Include('my2.js');

myFunc1('blarg');
myFunc2('bleet');

Je veux juste éviter de trop connaître les internautes et pouvoir juste dire: "Je souhaite utiliser ce module, et maintenant je vais utiliser du code à partir de celui-ci."

71
Josh Johnson

Vous pouvez créer votre élément <script> Avec un gestionnaire "onload", qui sera appelé lorsque le script aura été chargé et évalué par le navigateur.

var script = document.createElement('script');
script.onload = function() {
  alert("Script loaded and ready");
};
script.src = "http://whatever.com/the/script.js";
document.getElementsByTagName('head')[0].appendChild(script);

Vous ne pouvez pas le faire de manière synchrone.

edit - Il a été souligné que, fidèle à sa forme, IE ne déclenche pas d'événement "load" sur les balises <script> en cours de chargement. Ainsi, je suppose que la prochaine chose à faire serait de récupérer le script avec une requête XMLHttpRequest puis eval() vous-même (ou, je suppose, de fourrer le texte dans une balise <script> vous ajoutez: l'environnement d'exécution de eval() est affecté par la portée locale, il ne fera donc pas nécessairement ce que vous voulez qu'il fasse.)

edit - À compter de début 2013 , je vous conseillerais vivement de rechercher un outil de chargement de script plus robuste, tel que - Requirejs . Il y a beaucoup de cas spéciaux à s'inquiéter. Pour des situations vraiment simples, il y a yepnope , qui est maintenant intégré à Modernizr .

117
Pointy

Ce n'est pas joli, mais ça marche:

<script type="text/javascript">
  document.write('<script type="text/javascript" src="other.js"></script>');
</script>

<script type="text/javascript">
  functionFromOther();
</script>

Ou

<script type="text/javascript">
  document.write('<script type="text/javascript" src="other.js"></script>');
  window.onload = function() {
    functionFromOther();
  };
</script>

Le script doit être inclus dans une balise distincte <script> Ou avant window.onload().

Cela ne fonctionnera pas:

<script type="text/javascript">
  document.write('<script type="text/javascript" src="other.js"></script>');
  functionFromOther(); // Error
</script>

La même chose peut être faite avec la création d'un nœud, comme l'a fait Pointy, mais uniquement en FF. Vous n'avez aucune garantie que le script sera prêt dans d'autres navigateurs.

Étant un puriste XML, je déteste vraiment ça. Mais cela fonctionne de manière prévisible. Vous pouvez facilement envelopper ces vilains document.write() afin que vous n'ayez pas à les regarder. Vous pouvez même faire des tests, créer un nœud et l'ajouter, puis vous replier sur document.write().

23
Josh Johnson

C'est très tard, mais pour toute référence future à ceux qui voudraient le faire, vous pouvez utiliser ce qui suit:

function require(file,callback){
    var head=document.getElementsByTagName("head")[0];
    var script=document.createElement('script');
    script.src=file;
    script.type='text/javascript';
    //real browsers
    script.onload=callback;
    //Internet Explorer
    script.onreadystatechange = function() {
        if (this.readyState == 'complete') {
            callback();
        }
    }
    head.appendChild(script);
}

J'ai écrit un bref article de blog dessus il y a quelque temps http://crlog.info/2011/10/06/dynamically-requireinclude-a-javascript-file-into-a-page-and-be-notified -when-its-chargé /

16
zcourts

La programmation asynchrone est légèrement plus compliquée car la conséquence d'une demande est encapsulé dans une fonction au lieu de suivre l'instruction de demande. Mais le comportement en temps réel que l'utilisateur peut expérimenter peut être significativement mieux , car ils ne verront pas de serveur ni de réseau stagnants, le navigateur agissant comme s'il s'était écrasé. La programmation synchrone est irrespectueuse et ne devrait pas être utilisé dans des applications utilisées par des personnes.

Douglas Crockford ( YUI Blog )

Très bien, bouclez vos sièges, parce que ça va être un parcours cahoteux. De plus en plus de personnes demandent à charger des scripts de manière dynamique via JavaScript, cela semble être un sujet d'actualité.

Les principales raisons pour lesquelles cela est devenu si populaire sont:

  • modularité côté client
  • gestion des dépendances plus facile
  • la gestion des erreurs
  • avantages de performance

A propos de modularité : il est évident que la gestion des dépendances côté client doit être gérée directement sur le côté client. Si un certain objet, module ou bibliothèque est nécessaire, il suffit de le demander et de le charger dynamiquement.

Gestion des erreurs : si une ressource échoue, nous avons toujours la possibilité de bloquer uniquement les parties qui dépendent du script affecté, ou peut-être même d'essayer à nouveau. un peu de retard.

Les performances sont devenues un avantage concurrentiel entre les sites Web. Elles constituent désormais un facteur de classement dans les recherches. Les scripts dynamiques peuvent simuler un comportement asynchrone, contrairement à la méthode de blocage par défaut utilisée par les navigateurs pour gérer les scripts. Blocage des scripts autres ressources, blocage des scripts analyse ultérieure du document HTML, les scripts bloquent l'interface utilisateur. Désormais, avec les balises de script dynamiques et ses alternatives inter-navigateurs, vous pouvez effectuer de véritables requêtes asynchrones et exécuter du code dépendant uniquement lorsqu'elles sont disponibles. Vos scripts se chargeront en parallèle même avec d'autres ressources et le rendu sera parfait.

La raison pour laquelle certaines personnes s'en tiennent au script synchrone est qu'elles y sont habituées. Ils pensent que c'est le moyen par défaut, le moyen le plus facile, et certains peuvent même penser que c'est le seul moyen.

Mais la seule chose dont nous devons nous préoccuper au moment de décider de la conception d’une application est l’expérience utilisateur . Et dans ce domaine, asynchrone ne peut pas être battu. L'utilisateur obtient des réponses immédiates (ou dit des promesses), et une promesse vaut toujours mieux que rien. Un écran vide effraie les gens. Les développeurs ne doivent pas être paresseux pour améliorer les performances perçues .

Et enfin quelques mots sur le côté sale. Ce que vous devez faire pour que cela fonctionne avec tous les navigateurs:

  1. apprendre à penser de manière asynchrone
  2. organisez votre code pour qu'il soit modulaire
  3. organiser votre code pour bien gérer les erreurs et les cas Edge
  4. améliorer progressivement
  5. toujours prendre soin de la bonne quantité de commentaires
7
gblazex

Les réponses ci-dessus m'ont orienté dans la bonne direction. Voici une version générique de ce que je travaille:

  var script = document.createElement('script');
  script.src = 'http://' + location.hostname + '/module';
  script.addEventListener('load', postLoadFunction);
  document.head.appendChild(script);

  function postLoadFunction() {
     // add module dependent code here
  }      
4
James

Cela ressemble à une vue d'ensemble du chargement de script dynamique: http://unixpapa.com/js/dyna.html

3
morgancodes

J'ai eu le (s) problème (s) suivant (s) avec les réponses existantes à cette question (et des variantes de cette question sur d'autres threads stackoverflow):

  • Aucun du code chargé n'était débogable
  • De nombreuses solutions nécessitaient des rappels pour savoir quand le chargement était terminé au lieu d'un blocage réel, ce qui voulait dire que j'obtiendrais des erreurs d'exécution en appelant immédiatement le code chargé (c'est-à-dire le chargement).

Ou un peu plus précisément:

  • Aucun des codes chargés n'était débogable (à l'exception du bloc de balises de script HTML, si et seulement si la solution ajoutait un élément de script au dom, et jamais en tant que script visualisable individuel.) => Etant donné le nombre de scripts que je dois charger (et déboguer), c'était inacceptable.
  • Les solutions utilisant les événements 'onreadystatechange' ou 'onload' n'ont pas réussi à se bloquer, ce qui posait un problème important depuis que le code chargeait à l'origine des scripts dynamiques de manière synchrone avec 'require ([nom du fichier,' dojo/domReady ']);' et je me déshabillais du dojo.

Ma solution finale, qui charge le script avant de renvoyer, a tous les scripts correctement accessibles dans le débogueur (pour Chrome au moins)] est la suivante:

ATTENTION: le code suivant ne devrait PROBABLEMENT être utilisé qu'en mode 'développement'. (Pour le mode 'release', je recommande le préemballage et la minification SANS chargement de script dynamique, ou à moins sans eval).

//Code User TODO: you must create and set your own 'noEval' variable

require = function require(inFileName)
{
    var aRequest
        ,aScript
        ,aScriptSource
        ;

    //setup the full relative filename
    inFileName = 
        window.location.protocol + '//'
        + window.location.Host + '/'
        + inFileName;

    //synchronously get the code
    aRequest = new XMLHttpRequest();
    aRequest.open('GET', inFileName, false);
    aRequest.send();

    //set the returned script text while adding special comment to auto include in debugger source listing:
    aScriptSource = aRequest.responseText + '\n////# sourceURL=' + inFileName + '\n';

    if(noEval)//<== **TODO: Provide + set condition variable yourself!!!!**
    {
        //create a dom element to hold the code
        aScript = document.createElement('script');
        aScript.type = 'text/javascript';

        //set the script tag text, including the debugger id at the end!!
        aScript.text = aScriptSource;

        //append the code to the dom
        document.getElementsByTagName('body')[0].appendChild(aScript);
    }
    else
    {
        eval(aScriptSource);
    }
};
3
jeremykentbgross
function include(file){
return new Promise(function(resolve, reject){
        var script = document.createElement('script');
        script.src = file;
        script.type ='text/javascript';
        script.defer = true;
        document.getElementsByTagName('head').item(0).appendChild(script);

        script.onload = function(){
        resolve()
        }
        script.onerror = function(){
          reject()
        }
      })

 /*I HAVE MODIFIED THIS TO  BE PROMISE-BASED 
   HOW TO USE THIS FUNCTION 

  include('js/somefile.js').then(function(){
  console.log('loaded');
  },function(){
  console.log('not loaded');
  })
  */
}
3
Daggie Blanqx

Je suis habitué à avoir plusieurs fichiers .js sur mon site Web qui dépendent les uns des autres. Pour les charger et vérifier que les dépendances sont évaluées dans le bon ordre, j'ai écrit une fonction qui charge tous les fichiers, puis, une fois qu'ils sont tous reçus, eval(). Le principal inconvénient est que puisque cela ne fonctionne pas avec CDN. Pour de telles bibliothèques (par exemple, jQuery), il est préférable de les inclure de manière statique. Notez que l’insertion de noeuds de script dans le code HTML dynamiquement ne garantit pas que les scripts sont évalués dans le bon ordre, du moins pas dans Chrome (c’est la raison principale de écrire cette fonction).

function xhrs(reqs) {
  var requests = [] , count = [] , callback ;

  callback = function (r,c,i) {
    return function () {
      if  ( this.readyState == 4 ) {
        if (this.status != 200 ) {
          r[i]['resp']="" ;
        } 
        else {
          r[i]['resp']= this.responseText ;
        }
        c[0] = c[0] - 1 ;
        if ( c[0] == 0 ) {
          for ( var j = 0 ; j < r.length ; j++ ) {
            eval(r[j]['resp']) ;
          }
        }
      }
    }
  } ;
  if ( Object.prototype.toString.call( reqs ) === '[object Array]' ) {
    requests.length = reqs.length ;
  }
  else {
    requests.length = 1 ;
    reqs = [].concat(reqs);
  }
  count[0] = requests.length ;
  for ( var i = 0 ; i < requests.length ; i++ ) {
    requests[i] = {} ;
    requests[i]['xhr'] = new XMLHttpRequest () ;
    requests[i]['xhr'].open('GET', reqs[i]) ;
    requests[i]['xhr'].onreadystatechange = callback(requests,count,i) ;
    requests[i]['xhr'].send(null);
  }
}

Je n'ai pas trouvé comment faire des références à la même valeur sans créer un tableau (pour count). Sinon, je pense que cela va de soi (lorsque tout est chargé, eval(), chaque fichier dans l'ordre indiqué, sinon, stocke simplement la réponse).

Exemple d'utilisation:

xhrs( [
       root + '/global.js' ,
       window.location.href + 'config.js' ,
       root + '/js/lib/details.polyfill.min.js',
       root + '/js/scripts/address.js' ,
       root + '/js/scripts/tableofcontents.js' 
]) ;
1
user1251840

Ironiquement, j'ai ce que vous voulez, mais vous voulez quelque chose de plus proche de ce que vous aviez.

Je charge des choses de manière dynamique et asynchrone, mais avec un callback load comme ceci (en utilisant dojo et xmlhtpprequest)

  dojo.xhrGet({
    url: 'getCode.php',
    handleAs: "javascript",
    content : {
    module : 'my.js'
  },
  load: function() {
    myFunc1('blarg');
  },
  error: function(errorMessage) {
    console.error(errorMessage);
  }
});

Pour une explication plus détaillée, voir ici

Le problème est que, quelque part sur la ligne, le code est évalué et que, s'il y a quelque chose qui ne va pas avec votre code, l'instruction console.error(errorMessage); indiquera la ligne où eval() est, et non l'erreur réelle. C'EST UN TEL gros problème que j'essaie en fait de reconvertir en déclarations <script> (Voir ici .

0
puk