0

I'm trying to dynamically load the code of jquery.min.js and then the code of a custom JS file and use the functions from that custom file in my code, outside of the ajax call. Here is the code:

        var xhttp = new XMLHttpRequest();
        xhttp.onreadystatechange = function() {
                if (this.readyState == 4 && this.status == 200) {
                        eval(this.responseText);
                        $.post('/ajax.php?action=js', {
                            'action': 'custom'
                        }, function (response) {
                            eval(response);
                            // the response contains
                            // function custom1 () {...}
                            // ...
                            // function custom200 () {...}
                        });
                }
                xhttp.open("POST", "/ajax.php?action=js", true);
                xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
                xhttp.send("action=jquery");
        }
        // some code here and then calling the function
        function somefunc () {
            // there is other code here, checking if the ajax response is present
            // and then calling the function:
            custom1();
        }
        // calling somefunc(), which checks and calls the "undefined" function
        somefunc();

However I get Error: ReferenceError: custom1 is not defined. The issue is most probably not caused by the async, I check if a var created inside the response function is visible here and it is, if it's not setTimeout until it is and then execute the function.

Does somebody know why is that and how can I make the function to be used outside the Ajax call (I checked it in it and there it is defined, but not outside)?

Note that there are some 200 odd functions there with different names I need to use in random parts of the code.

Charlie
  • 22,886
  • 11
  • 59
  • 90
Lanexbg
  • 1,071
  • 1
  • 12
  • 17
  • 1
    AJAX is not the best tool to load scripts. Is ajax.php somehow filtering the functions, or does it just read and pass all the functions from a .js file? – Teemu Jul 30 '20 at 11:37
  • You have two separate ajax requests to the same endpoint, one via XmlHttp and one via $.post. – James Jul 30 '20 at 11:37
  • @Teemu it returns all the code of the file – Lanexbg Jul 30 '20 at 11:39
  • @James, that's correct, it depends on the parameters given what code will be returned to be evaluated. The fists ajax is to load the jquery.min.js code and when it's loaded a second call is made to load the custom JS file. – Lanexbg Jul 30 '20 at 11:41
  • Then you could just create a script tag, and load the script directly from the file. See ex. https://stackoverflow.com/a/3858014/1169519 – Teemu Jul 30 '20 at 11:42
  • @Teemu The AJAX calls are POST. You can't do that with a script tag. – AKX Jul 30 '20 at 11:44
  • @Teemu, will this work, if the JS file is blocked by a blocking extension/addon? – Lanexbg Jul 30 '20 at 11:44
  • @AKX True, but most likely you can do the AJAX post distinctly to the script tag creation, ex. create the tag within the response handler. – Teemu Jul 30 '20 at 11:46
  • @Teemu You mean you'd make an AJAX POST to an endpoint that would return a script tag with inline code you'd then inject to the document? That's no better than `eval`... – AKX Jul 30 '20 at 11:48
  • @AKX No, just make a separate AJAX call. And when the post responds, then create and append the tag, and load the script normally using `src` attribute. If the server selects a file based on the AJAX request, it could send the URL as a response, and you could populate the `src` attribute with the URL. If you want, you can set an onload listener for the script, then you'd know when it's ready to use. – Teemu Jul 30 '20 at 11:52
  • Do the script requests *need* to be POST? – James Jul 30 '20 at 11:59
  • @James, I'm not sure. I made the POST, because the responses are huge (the custom JS file is above 6K lines) and I don't know if GET has a limitation for the response as well as for the params. – Lanexbg Jul 30 '20 at 12:02
  • There's no restriction on GET or POST response sizes so you're good either way. 2 things about GET - the params are exposed in the URL (not good for sensitive data) and there is the limit of what you can send (from browser to server). Your case, GET makes sense and then you can just use ordinary script tags or load dynamic scripts via $.getScript as you like. – James Jul 30 '20 at 12:08
  • @James, will this work, if the JS file is blocked by a blocking extension/addon? – Lanexbg Jul 30 '20 at 12:09
  • In that case probably not. But you shouldn't try to circumvent blockers, that's a good way to lose users. – Teemu Jul 30 '20 at 12:14
  • Yeah I don't know, maybe that's why you're using POST, I don't usually consider such things. – James Jul 30 '20 at 12:15
  • @Teemu, if they block the files they will not be able to use the site, many features depend on the JS working, half the site is not even visible, if the JS files are blocked, that's why I need to always have the code loaded. – Lanexbg Jul 30 '20 at 12:20
  • Well, blockers are usually checking the URL of the file, they search for certain key words like "ads" etc. depending on purpose of the blocker. Change your paths to something less attractive to the blockers. – Teemu Jul 30 '20 at 12:23

1 Answers1

0

We can reformulate this as a pretty simple fetch() call with promises that ensure the requests have been made before you attempt to call custom1:

function evaluateAjaxActionJs(action) {
  // Concoct a form data object with action=<action>
  const body = new FormData();
  body.append("action", action);
  // Do a POST request with the form-data body...
  return fetch("/ajax.php?action=js", {
    method: "POST",
    body,
  })
    .then((r) => r.text())  // Interpret the result as text
    .then((text) => eval(text));  // ... and evaluate it :(
}

// Do both requests in parallel...
Promise.all([
  evaluateAjaxActionJs("jquery"),
  evaluateAjaxActionJs("js"),
]).then(() => {
  // ... and once the requests are done, call `custom1()`.
  custom1();
});

Error handling is elided from this example (you'll want to probably add a .catch((err) => ...), as well as check that the fetch returned an OK response (r.ok) before evaluating it.

If you need to first load the jquery thing, then the other, change this to

 evaluateAjaxActionJs("jquery")
  .then(() => evaluateAjaxActionJs("js"))
  .then(() => {
    // ...
  });
AKX
  • 152,115
  • 15
  • 115
  • 172
  • I will have to use the Promise every time I want to use a function from the custom file? – Lanexbg Jul 30 '20 at 11:46
  • No, but all code that needs the custom functions will need to be called from the last `.then(() => ` one way or another. – AKX Jul 30 '20 at 11:47
  • I'm calling different functions from different files, some of them are injected in multiple
    on the same page. Does this mean that I will have to load the custom JS code in every file (which will cause it to be loaded 1-2-3 times in a singe page?
    – Lanexbg Jul 30 '20 at 11:53
  • You can of course add some logic to prevent loading the same code several times in a page. – AKX Jul 30 '20 at 11:57