0

I'd like to convert a working javascript function that uses XMLHttpRequest() (or ActiveXObject, if necessary) to perform a synchronous data fetch from the server into an asynchronous one, if it can be done without undue pain. This function today "caches" the data so received, so it only needs to be fetched once. When I do the "simple" thing of merely selecting the asynchronous option of XMLHttpRequest() to perform the fetch asynchronously using the provided-for call-back feature, callers to my function don't get their data because it typically hasn't been returned yet from the server, and so the moment is lost. After the data has returned, of course there's no problem, but since many calls must come from onload, they aren't easily repeated. (Is there a way to "fire" all the functions in an inload list after the load as completed? This might be an alternative way around the issue.)

When, instead, it's done synchronously, the system works fine. There's a slight delay for some things until the data returns, but it's not enough, typically, to be an issue (so far). However, this usage is deprecated. Firefox's developer's console, for example, reports:

Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help http://xhr.spec.whatwg.org/

Since I don't really know any other mechanism (which is what this question is all about), I was thinking that I'd have to make my function that calls XMLHttpRequest() somehow track the calls it receives from other functions and take on the responsibility of performing whatever they need done after data has been received. Calling functions could, perhaps, be broken into two parts, one that requests the data, another that gets done whatever is needed when the data has shown up. The function using XMLHttpRequest() could implement this, for example, by adding a number of arguments to its function signature, such as what function to later call and so forth. ... I think it could be done but it's an unweildy mess. There must be a better way, I'm just trying to come up with one.

To be clear, the code works as synchronous right now. I'm looking for an easier way to make it asynchronous. It doesn't seem that easy!

Here's a snippet of the code that does it synchronously:

    var xmlhttp;
    var dn = window.location.hostname;
    var srcURL = "http://"+dn+"/unpublished/latest/this.txt";
    if (window.XMLHttpRequest)
    {
      // code for IE7+, Firefox, Chrome, Opera, Safari
      xmlhttp = new XMLHttpRequest();
    } else {// code for IE6, IE5
      xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
    xmlhttp.open("POST", srcURL, false);
    xmlhttp.setRequestHeader("Content-type",
     "application/x-www-form-urlencoded");
    xmlhttp.send("fname=Who&lname=Cares");
    if (xmlhttp.readyState == 4 && xmlhttp.status==200)
    {
      var siteData = xmlhttp.responseText;
      . . . 

The asynchronous version - that works fine in fetching the data, but too late for some callers - is rather similar. Here's a snippet of it:

if (window.XMLHttpRequest)
{
  // code for IE7+, Firefox, Chrome, Opera, Safari
  xmlhttp = new XMLHttpRequest();
} else {// code for IE6, IE5
  xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
//the callback function to be called when AJAX request comes back
xmlhttp.onreadystatechange = function()
{
  if (xmlhttp.readyState == 4 && xmlhttp.status==200)
  {
    var siteData = xmlhttp.responseText;
... etc ...

Here's an example of what the callers to look like. Note that getSiteData() is the function that contains the snippets above. To wit:

  <script type = "text/javascript">
    function setLinkToContact(objId)
    {
      document.getElementById(objId).setAttribute('href',
       getSiteData('contact'));
    }
  </script>

There are MANY such callers for one "getSiteData()" function.

Richard T
  • 4,570
  • 5
  • 37
  • 49
  • 1
    Seems like [Promise.all()](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) could help? – paibamboo Jul 22 '17 at 02:18
  • Are you trying to request data from server and receive response before `document` `DOMContentLoaded` or `window` `load` event? What is specific requirement? – guest271314 Jul 22 '17 at 02:21
  • @paibamboo Never heard of it - wish the examples were a bit more use-case oriented as I'm not sure I get how I'd use it, but looks like it's worth some time investigating... Thanks. – Richard T Jul 22 '17 at 02:25
  • @guest271314 Absolutely not, though that would certainly solve the problem! :-) The issue is simply that if I'm using the onload mechanism to get the changes implemented, they're usually WAY faster to get called than the network is apparently responding in many cases. The way I have it implemented now, callers only get their data if the asynchronous call has already completed. Otherwise, there's no data and poof, they're now history. Since there's no great way to get them to stop and wait, the moment is lost. – Richard T Jul 22 '17 at 02:28
  • 1
    _"Absolutely not, though that would certainly solve the problem! :-)"_ You can use `ServiceWorker` to serve the content that you expect to be defined in `document` when user initially requests `document`, see https://stackoverflow.com/a/41414247/. Not sure why you do not include the data within the served `document`? Probably still not gathering what you are trying to achieve. – guest271314 Jul 22 '17 at 02:31
  • @guest271314 Hmmm... I don't want the function(s) to have to anticipate or have knowledge about what the content of the document is per se. Rather, there's a bit of data that's often helpful and if the function is put in the "masthead" section of every page, this helpful data can be loaded via onload invoking a function when a page loads. Then, if and when that content is helpful, authors of individual pages on the site can attach simple calls to their html elements to get this data inserted into those "content" areas. So, as ServiceWOrker is new to me, IDK if it's going to help or not! :-) – Richard T Jul 22 '17 at 02:42
  • What is the data that the function is expected to return? – guest271314 Jul 22 '17 at 02:44
  • @guest271314 I was thinking maybe of converting the xmlhttp.open call to synchronous. . . MAYBE that would only block the callers to the routine to get data? If so, that's maybe a clean solution. Meanwhile, the function fetches a line of data from the server that may contain any of quite a number of sets of data including keyword=value information and other server-side cues to what the client should do, one of which is the person to contact for that particular page, should a viewer wish to contact someone - that's the example above. – Richard T Jul 22 '17 at 03:03
  • Still not gathering what you are trying to achieve. Or what issue is with current approach – guest271314 Jul 22 '17 at 03:05
  • @guest271314 care to take this discussion to a chat thread? (never done that before, but there's always a first time!) – Richard T Jul 22 '17 at 03:08
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/149857/discussion-between-guest271314-and-richard-t). – guest271314 Jul 22 '17 at 03:10
  • I'm having a hell of a time understanding both your problem and your goal here.. you should seriously rewrite your question to be clearer with a lot less text and a practical example of the page before and after it has been updated that explains VERY CLEARLY why you think this can only be done in the way you are going about it now. – myfunkyside Jul 22 '17 at 05:17
  • @myfunkyside @guest271314@paibamboo I've rewritten the question to hopefully clarify it. – Richard T Jul 23 '17 at 21:19
  • @RichardT Have you figured this out? I just saw your last comment (guessing you didn't mention me correctly). – paibamboo Oct 02 '17 at 09:07

1 Answers1

0

I read your question multiple times trying to understand it. Multiple pages require data from an async function call. Each page has unique data requirements that is dependent upon the async callback.

If that’s correct, here’s an approach you can try.

At the top of each page and before the async request kicks off, declare an empty global function on each page.

window.myAsyncCallback = function(){}

Have each page declare a function to handle the async callback.

page1 
var handleCallback1 = function(data){
    // unique handler 1
}

page2 
var handleCallback2 = function(data){
// unique handler 2
}

Assign that handlerCallback1 and handlerCallback1 to myAsyncCallback.

window.myAsyncCallback = handleCallback1;
window.myAsyncCallback = handleCallback2;

Then inside your onreadystatechange function. Execute the window.myAsyncCallback(data) with data. Each concrete implementation will trigger when data is available. Then you can use it how ever you want now that it’s available.

**********UPDATE UPDATE********

Are you able to update calls to “getSiteData(‘contact’)?” If so, maybe consider calling getSiteData.call() function and passing it reference to the dom element that needs the network data. Then inside getSiteData, set the element’s dataset value. Store the element in an array so you can retrieve it after the net request is complete. Then loop through the elements list, look for it’s value, and update as needed since you’ll have reference to the dom element.

If you are unable to modify the caller function, then try messing around with Function prototyping. Refactoring might be a lot of work but seems like the best option to avoid introducing race conditions. If you refactor for asynch, function callbacks should work for you without having to get into Promises and EventListeners; both of which are still great options but callbacks are lean for your needs.

I’m curious to know how you solve it. Keep me posted if possible.

<title></title>
    <script>
        var elements = [];

        var getSiteData = function(elem, value){
            // set element data
            elem.dataset = value;

            // store
            elements.push(elem);

            // mimic async request
            setTimeout(function(){

                var xmlHttpData = {
                    "contact":"http://www.yahoo.com",
                    "contacttwo":"http://www.google.com"
                }

                linklist.forEach(function(element, index, arr){
                    // match dataset value in element to xmlHttpData. 
                    //element.dataset.value 
                })

            },3000);

        }

        function setLinkToContact(objId){
            document.getElementById(objId).setAttribute('href', 
            getSiteData.call(document.getElementById(objId),'contact'));
        }

    </script>

<body>
    <a id="link" href="">my link</a>
    <div id="linktwo">my link</a>
    <script>
        setLinkToContact('link');
        setLinkToContact('linktwo');
    </script>
</body>
Nuspeed1
  • 126
  • 6
  • That's great and addresses an even broader problem: that all the pages the user loads in their browser could use the same data and it could be fetched just the one time per session. However, that's not really the issue here; I was looking for either a "light-weight" or at least already implemented mechanism to resolve the asnychronous vs synchronous problem. That is, if I make the existing function synchronous, it all works, if asynchronous, the calls that come in before the data is available are lost, so I'd have to catch the data of what's needed to be done to be executed later. Not easy. – Richard T Jul 22 '17 at 17:47
  • I rewrote the question to hopefully clarify it. – Richard T Jul 23 '17 at 21:19
  • @RichardT So there are X number of calls to getSiteData which render element values through a network request. This is why synch works and asych does not. Now I get it. Updated my answer. It might bring another perspective you can try. – Nuspeed1 Jul 24 '17 at 07:50