0

I have plenty of JavaScript files, and take three as example: App.js, ChildApp.js(more than one),and AjaxManager.js, while AjaxManger.js is what I add.

ChildApp extends App, and App dependent on AjaxManager.

In App's constructors:

function App() {
    this.ajaxManager=new AjaxManager();//Need AjaxManager.js to import, especially when new ChildApp is called.
    ....
}

ChildApp's constructor:

function ChildApp's() {
    App.call(this); //
}

ChildApp.prototype = new App(); 
...

In the legacy code, there are tens of places where include App.js and I don't want to copy and paste the same code that includes AjaxManager.js before there.

But if I don't include AjaxManager well, I will have error when

var app=new ChildApp();//ReferenceError: AjaxManager is not defined

So I use below code IN THE BEGINNING of App.js:

if(typeof AjaxManager =='undefined'){
    console.log('AjaxManager is not loaded , getting the script');
    $.ajax({
        url: .....//Url to load
        dataType: "script",
        async:false// How can I use async =true 
    });
}

But I have to use async:false for the request,which shows me a warning in the console that :Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience

Or I will have the error:

ReferenceError: AjaxManager is not defined

How can I use asynchronous request to load the script and sure things are working but will not break my existing functionality.

Update:

  1. I tried $.getScript(url) in the beigining of App.js or in the begining of function App() before I ask this question but the same error, I guess something relative to the constructor App should be place in the callback but I don't know how.

  2. I hope that I can modify app.js or ajaxmanager.js only since there are too many places where call new App() or new ChildApp()

JaskeyLam
  • 15,405
  • 21
  • 114
  • 149

2 Answers2

1

Don't use async: false. It's extremely bad practice as it will block the UI thread until the request has completed.

You can instead use $.getScript to shorten the code, and use the callback handler to execute any code reliant on the script being loaded. Try this:

if (typeof AjaxManager =='undefined') {
    $.getScript('ajaxManager.js', processPage);
}
else {
     // AjaxManager already loaded
     processPage();
}

function processPage() {
    // put your logic that relies on AjaxManager here 
}
Rory McCrossan
  • 331,213
  • 40
  • 305
  • 339
  • I have to return do something in the constructor `App` where has dependent on AjaxManager, how can return the instance of App after loading the script?Also, please check my updated question. – JaskeyLam Feb 06 '15 at 08:30
0

If you want to load three scripts in a specific order with jQuery, you can use $.getScript() and chain each one into the completed promise of the previous one.

$.getScript('AjaxManager.js').then(function() {
    return $.getScript('App.js');
}).then(function() {
    return $.getScript('ChildApp.js');
});

Or, if you wanted to make this into a function that you could pass an array of scripts to:

function loadScripts(array) {
    return array.reduce(function(p, scriptFile) {
        return p.then(function() {
            return $.getScript(scriptFile);
        });
    }, $.Deferred().resolve().promise());
}

loadScripts('AjaxManager.js', 'App.js', 'ChildApp.js');

// The order of the scripts in the array is the order they will be loaded.

If you want to test to see if they have previously been loaded, then you could also pass in a test symbol for each script and incorporate that into the loading logic.

function loadScripts(array) {
    return array.reduce(function(p, obj) {
        return p.then(function() {
            if (typeof window[obj.testSymbol] === "undefined") {
                return $.getScript(scriptFile);
             } else {
                 return $.Deferred().resolve().promise();
             }
        });
    }, $.Deferred().resolve().promise());
}

loadScripts(
    {file: 'AjaxManager.js', testSymbol: 'AjaxManager'}, 
    {file: 'App.js', testSymbol: 'App'}, 
    {file: 'ChildApp.js', testSymbol: 'ChildApp'}
);    

This would allow you to try to load any given script as many times as you wanted and it would only actually load once.


FYI, $.getScript() is an asynchronous operation. It calls the callback you pass it when it is done loading or resolves the promise it returns. If you want some operation to only proceed after $.getScript() has finished loading, then you MUST put that code in a callback.

So, if you wanted to load the AjaxManager script inside of the App() constructor, you can't do that before the constructor returns. It is not recommended in Javascript that you initiate asynchronous operations in a constructor because there is no good way to do error handling and you may have a partially initialized object until the asynchronous operation has completed.

See the 2nd half of this answer for some comments about async operations in constructors.

Community
  • 1
  • 1
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Actually I tried using $.getScript in the beginning of App.js, but the same error. Would you please show me how to deal will constructors in the callback? – JaskeyLam Feb 06 '15 at 08:10
  • @Jaskey - See what I have added to the end of my answer about constructors. Generally, you don't want to put async operations that are needed for the initialization of your object in constructors for reasons spelled out in the second half of [this answer](http://stackoverflow.com/questions/28227480/javascript-async-constructor-pattern/28228236#28228236). – jfriend00 Feb 06 '15 at 08:19