document.createElement("script") synchronously
Asked Answered
S

12

99

Is it possible to call in a .js file synchronously and then use it immediately afterward?

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

This is simplified. In my implementation the createElement stuff is in a function. I thought about adding something to the function that could check to see if a certain variable was instantiated before returning control. But then there is still the problem of what to do when including js from another site that I have no control over.

Thoughts?

Edit:

I've accepted the best answer for now because it gives a good explanation for what's going on. But if anyone has any suggestions for how to improve this I'm open to them. Here's an example of what I'd like to do.

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

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

I just want to keep from having to know the internals too much and just be able to say, "I wish to use this module, and now I will use some code from it."

Strongroom answered 14/7, 2010 at 16:34 Comment(1)
I haven't figured out how to make references to the same value without creating an array (for count). Otherwise I think it is self-explanatory (when everything is loaded, eval() every file in the order given, otherwise just store the response).Puerilism
I
153

You can create your <script> element with an "onload" handler, and that will be called when the script has been loaded and evaluated by the browser.

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

You can't do it synchronously.

edit — it's been pointed out that, true to form, IE doesn't fire a "load" event on <script> tags being loaded/evaluated. Thus I suppose the next thing to do would be to fetch the script with an XMLHttpRequest and then eval() it yourself. (Or, I suppose, stuff the text into a <script> tag you add; the execution environment of eval() is affected by the local scope, so it won't necessarily do what you want it to do.)

editAs of early 2013, I'd strongly advise looking into a more robust script loading tool like Requirejs. There are a lot of special cases to worry about. For really simple situations, there's yepnope, which is now built into Modernizr.

Inquiry answered 14/7, 2010 at 16:50 Comment(6)
unfortunately it's not cross-browser.Unless
Really?? Who doesn't fire a "load" event when a script is loaded? Wait - don't tell me.Inquiry
@Inquiry I solved this problem by using XMLHttpRequest and then eval(). However, debugging it is a nightmare b/c the error message reports the line eval() appears, not the actual errorMongrel
But how does requirejs do this then?? How are they including many scripts and firing them in the right order?Intrauterine
Of course, document.write() is what you are looking for. Not pretty, but it works.Hoi
@momo requireJS and other module loaders use a method of dependency injection to ensure order (see the define([], <F>) callback semantics). This issue is related to asynchronous loading, which requireJS does do, but in a far different way then the above practice. The method employed by the OP is more commonly seen in 3rd party provider libraries (see Google Tag Manager) that will load scripts asynchronously that are not consumed by other scripts.Kenleigh
S
26

This isn't pretty, but it works:

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

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

Or

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

The script must be included either in a separate <script> tag or before window.onload().

This will not work:

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

The same can be done with creating a node, as Pointy did, but only in FF. You have no guarantee when the script will be ready in other browsers.

Being an XML Purist I really hate this. But it does work predictably. You could easily wrap those ugly document.write()s so you don't have to look at them. You could even do tests and create a node and append it then fall back on document.write().

Strongroom answered 20/7, 2010 at 17:42 Comment(6)
Are you sure that your first code snippet works in all browsers?Acidimeter
@BogdanGusiev I'm not 100% sure. I tested in IE 8, (the then current versions of) Firefox, and Chrome. Chances are this wouldn't work with XHTML doctypes that are served as content type application/xhtml+xml.Strongroom
Unfortunately script tags cannot be used in JS files.Chirrupy
@Chirrupy You could do a document.write("<SCR" + "IPT>" + "...").Catawba
This is an OK alternative for scripts inside <head> which load several other dependencies (or private files).Dewittdewlap
You should NOT use document.write() anymore. See: developers.google.com/web/updates/2016/08/… & varvy.com/pagespeed/avoid-document-write.htmlAlmire
F
22

This is way late but for future reference to anyone who'd like to do this, you can use the following:

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

I did a short blog post on it some time ago http://crlog.info/2011/10/06/dynamically-requireinclude-a-javascript-file-into-a-page-and-be-notified-when-its-loaded/

Frederick answered 5/1, 2013 at 16:50 Comment(4)
does this really work? see my question: #17978755Intrauterine
This looks interesting. One question... why is it necessary to execute the callback method twice? (script.onload=callback and callback() used in onreadystatechange)Chirrupy
onreadysteatechange is for IE and will only fire on IE as the onload will no fire for IEMoss
Gotta love that the comments and script show "Real Browsers" and then the "IE" stuff! Haha! +1Stephanus
S
7

The answers above pointed me in the right direction. Here is a generic version of what I got working:

  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
  }      
Shenitashenk answered 16/3, 2016 at 2:41 Comment(2)
When is postLoadFunction() called?Strongroom
@JoshJohnson script.addEventListener('load', postLoadFunction); means postLoadFunction is called on script loading.Cavern
U
5

Asynchronous programming is slightly more complicated because the consequence of making a request is encapsulated in a function instead of following the request statement. But the realtime behavior that the user experiences can be significantly better because they will not see a sluggish server or sluggish network cause the browser to act as though it had crashed. Synchronous programming is disrespectful and should not be employed in applications which are used by people.

Douglas Crockford (YUI Blog)

Alright, buckle your seats, because it's going to be a bumpy ride. More and more people ask about loading scripts dynamically via javascript, it seems to be a hot topic.

The main reasons why this became so popular are:

  • client-side modularity
  • easier dependency management
  • error handling
  • performance advantages

About modularity: it is obvious that managing client-side dependencies should be handled right on the client-side. If a certain object, module or library is needed we just ask for it and load it dynamically.

Error handling: if a resource fails we still get the chance to block only the parts that depend on the affected script, or maybe even give it another try with some delay.

Performance has become a competitive edge between websites, it is now a search ranking factor. What dynamic scripts can do is mimic asynchronous behavior as opposed to the default blocking way of how browsers handle scripts. Scripts block other resources, scripts block further parsing of the HTML document, scripts block the UI. Now with dynamic script tags and its cross-browser alternatives you can do real asynchronous requests, and execute dependent code only when they are available. Your scripts will load in-parallel even with other resources and the rendering will be flawless.

The reason why some people stick to synchronous scripting is because they are used to it. They think it is the default way, it is the easier way, and some may even think it is the only way.

But the only thing we should care about when this needs to be decided concerning an applications's design is the end-user experience. And in this area asynchronous cannot be beaten. The user gets immediate responses (or say promises), and a promise is always better than nothing. A blank screen scares people. Developers shouldn't be lazy to enhance perceived performance.

And finally some words about the dirty side. What you should do in order to get it working across browsers:

  1. learn to think asynchronously
  2. organize your code to be modular
  3. organize your code to handle errors and edge cases well
  4. enhance progressively
  5. always take care of the right amount of feedback
Unless answered 14/7, 2010 at 22:41 Comment(7)
Thanks, galam. I guess I should have been more clear. I did expect this to be asynchronous in the end. I just want a way to access it that made logical sense to the programmer. I wanted to avoid things like: Import("package.mod1", function() { // do stuff with mod1 }); Import("package.mod2", function() { // do stuff with mod2 }); I took a look at your script and labjs and, while nice, seem to be more complex for my needs. I thought there may be a simpler way and wanted to avoid bringing in extra dependencies.Strongroom
You missed the point of my post. It's all about the users. This should be your first priority. Everything else is secondary.Unless
Galam, very good point. User experience is very important. To be clear, I'm not willing to sacrifice user experience OR quality, maintainable code. I'm going to look into closure and labjs to see what they can do for me. But for the time being I may need to stick with <script> tags. Unfortunately, I'm not working on this by myself. I work with a medium sized team of developers so maintainable code is high priority. If everyone can't figure out how to use the lib efficiently then user exp goes right out the window. Callbacks are intuitive. A callback because you imported a package are not.Strongroom
Again, for clarity, "synchronous" was a bad choice of words used to get my point across. I don't want the browser to freeze as things are loading.Strongroom
What about legacy issues? What if you have an old library many people use and depend on and there is just no way to eliminate the synchronous loading because of it. You can run a parallel newer version and try to rustle people into using it. But as long as they are paying customers you can't say use the new or nothing at all.Excellency
What if you need synchronous loading? If you actually need to block in order to preserve the user experience. If you are using a JavaScript based A/B or MVT testing system. How do you want to asynchronously load the content and replace the default without getting a flicker effect that ruins the user experience? I am open to suggestions. I have over 500 colleagues who would like to know a solution to this. If you do not have one, please do not come with expressions like "Synchronous programming is disrespectful and should not be employed in applications which are used by people.".Excellency
What if I need an importmap to load synchronously BEFORE any of my asynchronous modules? Everything is dependent on an importmap these days with native ES importing....Orvas
N
5
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');
  })
  */
}
Nutrient answered 3/11, 2017 at 11:45 Comment(0)
A
4

I had the following problem(s) with the existing answers to this question (and variations of this question on other stackoverflow threads):

  • None of the loaded code was debuggable
  • Many of the solutions required callbacks to know when loading was finished instead of truly blocking, meaning I would get execution errors from immediately calling loaded (ie loading) code.

Or, slightly more accurately:

  • None of the loaded code was debuggable (except from the HTML script tag block, if and only if the solution added a script elements to the dom, and never ever as individual viewable scripts.) => Given how many scripts I have to load (and debug), this was unacceptable.
  • Solutions using 'onreadystatechange' or 'onload' events failed to block, which was a big problem since the code originally loaded dynamic scripts synchronously using 'require([filename, 'dojo/domReady']);' and I was stripping out dojo.

My final solution, which loads the script before returning, AND has all scripts properly accessible in the debugger (for Chrome at least) is as follows:

WARNING: The following code should PROBABLY be used only in 'development' mode. (For 'release' mode I recommend prepackaging and minification WITHOUT dynamic script loading, or at least without 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);
    }
};
Abisha answered 15/2, 2014 at 8:29 Comment(0)
T
3

This looks like a decent overview of dynamic script loading: http://unixpapa.com/js/dyna.html

Trotskyite answered 14/7, 2010 at 16:38 Comment(0)
L
1

I am used to having multiple .js files on my web site that depend one on another. To load them and ensure that the dependencies are evaluated in the right order, I have written a function that loads all the files and then, once they are all received, eval() them. The main drawback is that since this does not work with CDN. For such libraries (e.g., jQuery) it is better to include them statically. Note that inserting script nodes in the HTML dynamically won't guarantee that scripts are evaluated in the right order, at least not in Chrome (this was the major reason for writing this function).

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

I haven't figured out how to make references to the same value without creating an array (for count). Otherwise I think it is self-explanatory (when everything is loaded, eval() every file in the order given, otherwise just store the response).

Usage example:

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' 
]) ;
Leblanc answered 29/8, 2013 at 13:14 Comment(0)
A
1

// ...

await import_script('https://cdnjs.cloudflare.com/ajax/libs/...js');

async function import_script(url) {

        const script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = url;


        document.head.appendChild(script);

        console.log(`import ${url} ...`);

        await new Promise((resolve) => script.onload = resolve);
    }
Antilog answered 20/6, 2021 at 20:29 Comment(0)
M
0

Ironically, I have what you want, but want something closer to what you had.

I am loading things in dynamically and asynchronously, but with an load callback like so (using dojo and xmlhtpprequest)

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

For a more detailed explanation, see here

The problem is that somewhere along the line the code gets evaled, and if there's anything wrong with your code, the console.error(errorMessage); statement will indicate the line where eval() is, not the actual error. This is SUCH a big problem that I am actually trying to convert back to <script> statements (see here.

Mongrel answered 24/10, 2011 at 3:1 Comment(3)
Fun fact: I too have gone back to <script> tags and using convention (along with some build packages) to just package my js in a way that makes sense.Strongroom
@JoshJohnson I'm not that lucky b/c I need to do a breadth first load of packages with scripts within rings being loaded asynchronously and scripts between rings being loaded synchronouslyMongrel
I was lucky and able to work something out. I don't envy your position.Strongroom
C
0

This works for modern 'evergreen' browsers that support async/await and fetch.

This example is simplified, without error handling, to show the basic principals at work.

// This is a modern JS dependency fetcher - a "webpack" for the browser
const addDependentScripts = async function( scriptsToAdd ) {

  // Create an empty script element
  const s=document.createElement('script')

  // Fetch each script in turn, waiting until the source has arrived
  // before continuing to fetch the next.
  for ( var i = 0; i < scriptsToAdd.length; i++ ) {
    let r = await fetch( scriptsToAdd[i] )

    // Here we append the incoming javascript text to our script element.
    s.text += await r.text()
  }

  // Finally, add our new script element to the page. It's
  // during this operation that the new bundle of JS code 'goes live'.
  document.querySelector('body').appendChild(s)
}

// call our browser "webpack" bundler
addDependentScripts( [
  'https://code.jquery.com/jquery-3.5.1.slim.min.js',
  'https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js'
] )
Canonist answered 18/7, 2020 at 14:22 Comment(1)
we cant say it like webpack ... 1. for every script, it is sending a new HTTP request, 2. This will also not check the dependencies between them, 3. Not all browsers support async/await and 4. Performance wise it we tedious then normal. It would be good to append this in headPhung

© 2022 - 2024 — McMap. All rights reserved.