86 votes

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

Est-il possible de faire appel à un .js de manière synchrone et de l'utiliser 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>

C'est simplifié. Dans mon implémentation, la création de l'élément est dans une fonction. J'ai pensé à ajouter quelque chose à la fonction qui pourrait vérifier si une certaine variable a été instanciée avant de retourner le contrôle. Mais alors il y a toujours le problème de ce qu'il faut faire quand on inclut des js d'un autre site sur lequel je n'ai aucun contrôle.

Qu'en pensez-vous ?

Editar:

J'ai accepté la meilleure réponse pour l'instant, car elle explique bien ce qui se passe. Mais si quelqu'un a des suggestions à faire pour améliorer la situation, je suis ouvert à toute suggestion. 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 d'avoir à trop connaître les éléments internes et pouvoir simplement dire : "Je souhaite utiliser ce module, et maintenant je vais utiliser du code de celui-ci".

0 votes

Je n'ai pas encore trouvé comment faire des références à la même valeur sans créer un tableau (pour le comptage). Sinon, je pense que c'est explicite (quand tout est chargé), eval() chaque fichier dans l'ordre donné, sinon il suffit de stocker la réponse).

139voto

Pointy Points 172438

Vous pouvez créer votre <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.

editar - il a été souligné que, fidèle à lui-même, IE ne déclenche pas d'événement "load" sur <script> balises en cours de chargement/évaluation. Ainsi, je suppose que la prochaine chose à faire serait de récupérer le script avec un XMLHttpRequest et ensuite eval() vous-même. (Ou, je suppose, mettre le texte dans une <script> que vous ajoutez ; l'environnement d'exécution de eval() est affecté par la portée locale, donc il ne fera pas nécessairement ce que vous voulez qu'il fasse).

editar - Depuis le début de l'année 2013 je vous conseille vivement de vous tourner vers un outil de chargement de script plus robuste, tel que Requirejs . Il y a beaucoup de cas particuliers à prendre en compte. Pour les situations vraiment simples, il y a yepnope qui est maintenant intégré dans Modernizr .

3 votes

Malheureusement, il n'est pas multi-navigateur.

70 votes

Vraiment ? Qui ne déclenche pas un événement "load" lorsqu'un script est chargé ? Attendez - ne me le dis pas.

1 votes

@Pointy J'ai résolu ce problème en utilisant XMLHttpRequest et ensuite eval() . Cependant, le débogage est un cauchemar, car le message d'erreur indique la ligne suivante eval() apparaît, pas l'erreur réelle

26voto

Josh Johnson Points 2312

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 un fichier séparé. <script> ou avant la balise 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 seulement dans FF. Vous n'avez aucune garantie quand le script sera prêt dans les autres navigateurs.

En tant que puriste du XML, je déteste vraiment cela. Mais cela fonctionne de manière prévisible. Vous pourriez facilement envelopper ces affreux document.write() afin de ne pas avoir à les consulter. Vous pourriez même faire des tests et créer un nœud et l'annexer puis vous rabattre sur document.write() .

0 votes

Êtes-vous sûr que votre premier extrait de code fonctionne dans tous les navigateurs ?

0 votes

@BogdanGusiev Je ne suis pas sûr à 100%. J'ai testé dans IE 8, (les versions actuelles de) Firefox et Chrome. Il est probable que cela ne fonctionne pas avec les doctypes XHTML qui sont servis comme type de contenu. application/xhtml+xml .

1 votes

Malheureusement, les balises script ne peuvent pas être utilisées dans les fichiers JS.

20voto

zcourts Points 921

C'est un peu tard, mais à titre de référence pour tous 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 publié un petit article sur ce sujet il y a quelque temps. http://crlog.info/2011/10/06/dynamically-requireinclude-a-javascript-file-into-a-page-and-be-notified-when-its-loaded/

0 votes

Cela fonctionne-t-il vraiment ? voir ma question : stackoverflow.com/questions/17978255/

1 votes

Cela semble intéressant. Une question... pourquoi est-il nécessaire d'exécuter deux fois la méthode de rappel (script.onload=callback et callback() utilisé dans onreadystatechange) ?

1 votes

Onreadysteatechange est pour IE et ne se déclenchera que sur IE, car onload ne se déclenchera pas sur IE.

6voto

galambalazs Points 24393

Asynchrone la programmation est légèrement plus compliqué parce que la conséquence d'une demande est encapsulée dans une fonction au lieu de suivre l'énoncé de la demande. Mais le comportement en temps réel que le expériences des utilisateurs peut être significativement meilleur parce qu'ils ne verront pas un serveur ou un réseau lent faire en sorte que le navigateur se comporte comme s'il avait planté. navigateur se comporte comme s'il avait planté. Synchrone la programmation est irrespectueux et ne devrait pas être employé dans les applications qui sont utilisées par les gens.

Douglas Crockford ( Blog YUI )

Très bien, attachez vos sièges, parce que ça va être un voyage mouvementé. De plus en plus de personnes posent des questions sur le chargement dynamique de scripts via javascript, cela semble être un sujet brûlant.

Les principales raisons de cette popularité sont les suivantes :

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

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

Traitement 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 de faire un nouvel essai avec un certain retard.

Performance est devenu un avantage concurrentiel entre les sites web, c'est maintenant un facteur de classement dans les recherches. Ce que les scripts dynamiques peuvent faire, c'est imiter le comportement asynchrone, par opposition à la façon dont les navigateurs gèrent les scripts, qui est bloquante par défaut. Bloc de scripts d'autres ressources, bloc de scripts la poursuite de l'analyse du document HTML, bloc de scripts l'interface utilisateur. Désormais, avec les balises dynamiques script et leurs alternatives inter-navigateurs, vous pouvez effectuer de véritables requêtes asynchrones et exécuter le code dépendant uniquement lorsqu'il est disponible. Vos scripts se chargeront en parallèle, même avec d'autres ressources, et le rendu sera impeccable.

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

Mais la seule chose dont nous devrions nous préoccuper lorsqu'il s'agit de prendre une décision concernant la conception d'une application, c'est de la expérience de l'utilisateur final . Et dans ce domaine, l'asynchrone ne peut être battu. L'utilisateur obtient réponses immédiates (ou dire des promesses), et une promesse est toujours mieux que rien. Un écran vide fait peur aux gens. Les développeurs ne devraient pas être paresseux pour améliorer la qualité de leurs produits. performance perçue .

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

  1. apprendre à penser de manière asynchrone
  2. organiser votre code pour qu'il soit modulaire
  3. organiser votre code pour bien gérer les erreurs et les cas limites
  4. améliorer progressivement
  5. toujours veiller à la bonne quantité de retour d'information

0 votes

Merci, Galam. Je suppose que j'aurais dû être plus clair. Je m'attendais à ce que ce soit asynchrone à la fin. Je veux juste un moyen d'y accéder qui ait un sens logique pour le programmeur. Je voulais éviter des choses comme : Import("package.mod1", function() { // faire des choses avec mod1 }) ; Import("package.mod2", function() { // faire des choses avec mod2 }) ; J'ai jeté un coup d'oeil à votre script et labjs et, bien que sympathique, semble être plus complexe pour mes besoins. Je pensais qu'il y avait peut-être un moyen plus simple et je voulais éviter d'apporter des dépendances supplémentaires.

1 votes

Vous n'avez pas compris le sens de mon message. Il s'agit des utilisateurs. Cela devrait être votre première priorité. Tout le reste est secondaire.

2 votes

Galam, très bon point. L'expérience utilisateur est très importante. Pour être clair, je ne suis pas prêt à sacrifier l'expérience utilisateur OU la qualité et la maintenabilité du code. Je vais me pencher sur closure et labjs pour voir ce qu'ils peuvent faire pour moi. Mais pour l'instant, je vais peut-être devoir m'en tenir aux balises <script>. Malheureusement, je ne travaille pas seul sur ce projet. Je travaille avec une équipe de développeurs de taille moyenne, de sorte que la maintenance du code est une priorité absolue. Si tout le monde ne peut pas comprendre comment utiliser la librairie de manière efficace, alors l'expérience de l'utilisateur passe à la trappe. Les callbacks sont intuitifs. Un callback parce que vous avez importé un paquet ne l'est pas.

4voto

J'ai rencontré le(s) problème(s) suivant(s) avec les réponses existantes à cette question (et les variations de cette question sur d'autres fils de discussion de stackoverflow) :

  • Aucun des codes chargés n'était débuggable.
  • De nombreuses solutions nécessitaient des rappels pour savoir quand le chargement était terminé, au lieu d'être réellement bloquantes, ce qui signifiait que j'obtenais des erreurs d'exécution en appelant immédiatement du code chargé (c'est-à-dire en cours de chargement).

Ou, de façon un peu plus précise :

  • Aucun des codes chargés n'était débuggable. (sauf à partir du bloc de balises HTML script, si et seulement si la solution a ajouté un élément script au dom, et jamais en tant que scripts individuels visualisables). \=> Étant donné le nombre de scripts que je dois charger (et déboguer), c'était inacceptable.
  • Les solutions utilisant les événements "onreadystatechange" ou "onload" ne bloquaient pas, ce qui était un gros problème puisque le code chargeait à l'origine les scripts dynamiques de manière synchrone en utilisant "require([filename, 'dojo/domReady']) ;" et que je supprimais dojo.

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

AVERTISSEMENT : Le code suivant ne doit PROBABLEMENT être utilisé qu'en mode 'développement'. (Pour le mode 'release', je recommande le prepackaging et la minification SANS chargement dynamique de script, ou au 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);
    }
};

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X