Dynamically loading JavaScript synchronously

后端 未结 18 2172
小蘑菇
小蘑菇 2020-11-27 13:55

I\'m using the module pattern, one of the things I want to do is dynamically include an external JavaScript file, execute the file, and then use the functions/variables in t

相关标签:
18条回答
  • 2020-11-27 14:39

    You can't and shouldn't perform server operations synchronously for obvious reasons. What you can do, though, is to have an event handler telling you when the script is loaded:

    tag.onreadystatechange = function() { if (this.readyState == 'complete' || this.readyState == 'loaded') this.onload({ target: this }); };
    
    tag.onload = function(load) {/*init code here*/}
    

    onreadystatechange delegation is, from memory, a workaround for IE, which has patchy support for onload.

    0 讨论(0)
  • 2020-11-27 14:39

    I've had a similar task a few days earlier, and here's how I did it.
    This loader works both in file:// prefixes as well as in http:// and https://, and is cross-browser compatible.
    It however, cannot load specific classes or functions as modules from scripts; it will load the whole script altogether and make it available to the DOM.

    // Loads a script or an array of scripts (including stylesheets)
    // in their respective index order, synchronously.
    // By Sayanjyoti Das @https://stackoverflow.com/users/7189950/sayanjyoti-das
    var Loader={
        queue: [], // Scripts queued to be loaded synchronously
        loadJsCss: function(src, onl) {
            var ext=src.toLowerCase().substring(src.length-3, src.length);
            if(ext=='.js') {
                var scrNode=el('script', null, null, null);
                scrNode.type='text/javascript';
                scrNode.onload=function() {onl();};
                scrNode.src=src;
                document.body.appendChild(scrNode);
            }else if(ext=='css') {
                var cssNode=el('link', null, null, null);
                cssNode.rel='stylesheet';
                cssNode.type='text/css';
                cssNode.href=src;
                document.head.appendChild(cssNode);
                onl();
            }
        },
        add: function(data) {
            var ltype=(typeof data.src).toLowerCase();
    
            // Load a single script
            if(ltype=='string') {
                data.src=data.src;
                Loader.queue.splice(0, 1, data, Loader.queue[0]);
                Loader.next();
            }
            // Load an array of scripts
            else if(ltype=='object') {
                for(var i=data.src.length-1; i>=0; i--) {
                    Loader.queue.splice(0, 1, {
                        src: data.src[i],
                        onload: function() {
                            if(Loader.next()==false) {
                                data.onload();
                                return;
                            }
                            Loader.next();
                        }
                    }, Loader.queue[0]);
                }
                Loader.next();
            }
        },
        next: function() {
            if(Loader.queue.length!=0 && Loader.queue[0]) {
                var scr=Loader.queue[0];
    
                // Remove the script from the queue
                if(Loader.queue.length>1)
                    Loader.queue.splice(0, 2, Loader.queue[1]);
                else
                    Loader.queue=[];
    
                // Load the script
                Loader.loadJsCss(scr.src, scr.onload);
            }else return false;
        }
    };
    

    The above function is very powerful and elegant; it allows you to load a single script or an array of script synchronously (i.e, next script not loaded until previous script loading finished). Moreover, a loaded script may load more scripts, which defers the queue in the parent script.

    BTW, a script here means a JavaScript file or a CSS stylesheet.

    Here's how to use it:-

    // Load a single script
    Loader.add({
        src: 'test.js',
        onload: function() {
            alert('yay!');
        }
    });
    
    // Load multiple scripts
    Loader.add({
        src: ['test1.js', 'test2.js', 'mystyles.css', 'test3.js'],
        onload: function() {
            alert('all loaded!');
        }
    });
    

    Note that, the onload function in the Loader arguments is called when all of the scripts have loaded, not when one or a single script is loaded.

    You can also load more scripts in the scripts you loaded, such as in test.js, test1.js, etc. By doing this, you will defer the load of the next parent script and the queue in the child script will be prioritized.

    Hope it helps :-)

    0 讨论(0)
  • 2020-11-27 14:41

    There is only one way to synchronously load and execute a script resource, and that is using a synchronous XHR

    This is an example of how to do this

    // get some kind of XMLHttpRequest
    var xhrObj = createXMLHTTPObject();
    // open and send a synchronous request
    xhrObj.open('GET', "script.js", false);
    xhrObj.send('');
    // add the returned content to a newly created script tag
    var se = document.createElement('script');
    se.type = "text/javascript";
    se.text = xhrObj.responseText;
    document.getElementsByTagName('head')[0].appendChild(se);
    

    But you shouldn't in general use synchronous requests as this will block everything else. But that being said, there are of course scenarios where this is appropriate.

    I would probably refactor the containing function into an asynchronous pattern though using an onload handler.

    0 讨论(0)
  • 2020-11-27 14:45

    The most Node.js-like implementation I could come up with was able to load JS files synchonously, and use them as objects/modules

    var scriptCache = [];
    var paths = [];
    function Import(path)
    {
        var index = 0;
        if((index = paths.indexOf(path)) != -1) //If we already imported this module
        {
            return scriptCache [index];
        }
    
        var request, script, source;
        var fullPath = window.location.protocol + '//' + window.location.host + '/' + path;
    
        request = new XMLHttpRequest();
        request.open('GET', fullPath, false);
        request.send();
    
        source = request.responseText;
    
        var module = (function concealedEval() {
            eval(source);
            return exports;
        })();
    
        scriptCache.push(module);
        paths.push(path);
    
        return module;
    }
    

    An example source (addobjects.js):

    function AddTwoObjects(a, b)
    {
        return a + b;
    }
    
    this.exports = AddTwoObjects;
    

    And use it like this:

    var AddTwoObjects = Import('addobjects.js');
    alert(AddTwoObjects(3, 4)); //7
    //or even like this:
    alert(Import('addobjects.js')(3, 4)); //7
    
    0 讨论(0)
  • 2020-11-27 14:47

    here is my code

    var loaded_script = [];
    function loadScript(urls, callback, sync) {
        var len = urls.length, count = 0;
    
        // check are all js loaded, then execute callback (if any)
        var check = function() {
            if (count == len) {
                callback && typeof callback=="function" && callback();
            }
        };
    
        for (var i = 0; i < len; i++) {
            var url = urls[i];
    
            // check if script not loaded (prevent load again)
            if (loaded_script.indexOf(url) == -1) {
                var script = document.createElement("script");
                script.type = "text/javascript";
    
                // set sync loading here (default is async)
                if (sync) {
                    script.async = false;
                }
    
                // script onload event
                if (script.readyState) {    // IE
                    script.onreadystatechange = function() {
                        if (script.readyState=="loaded" || script.readyState=="complete") {
                            script.onreadystatechange = null;
                            count++, check();
                        }
                    };
                } else {    // Others
                    script.onload = function() {
                        count++, check();
                    };
                }
    
                // add script to head tag
                script.src = url;
                document.getElementsByTagName("head")[0].appendChild(script);
    
                // mark this script has loaded
                loaded_script.push(url);
            } else {
                count++, check();
            }
        }
    }
    

    I use this on pjax site.

    loadScript(
        [
            "js/first.js",
            "js/second.js",
        ],
        function() {
            alert("Scripts loaded.");
        },
        true
    );
    
    0 讨论(0)
  • 2020-11-27 14:51

    The accepted answer is NOT correct.

    Loading a file synchronously is not the same as executing the file synchronously - which is what the OP requested.

    The accepted answer loads the file sync, but does nothing more than append a script tag to the DOM. Just because appendChild() has returned does not in anyway guarantee that the script has finished executing and it's members are initialised for use.

    The only (see caveat) way to achieve the OPs question is to sync load the script over XHR as stated, then read as text and pass into either eval() or a new Function() call and wait for that function to return. This is the only way to guarantee the script is loaded AND executed synchronously.

    I make no comment as to whether this is a wise thing to do either from a UI or security perspective, but there are certainly use cases that justify a sync load & execute.

    Caveat: Unless you're using web workers in which case just call loadScripts();

    0 讨论(0)
提交回复
热议问题