0

First, I've looked at related SO questions, and didn't find much in the way of a suitable answer, so here goes:

I've been working on an HTML/Javascript page that acts as a UI to a back-end server. I made some pretty good strides in completing it, all while using synchronous calls in AJAX (aka var xmlhttp = new XMLHttpRequest(); xmlhttp.open(type, action, false);), but have now come to find out that Mozilla apparently doesn't like synchronous requests, and has therefore deprecated some much-needed functionality from them.

To quote https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest:

Note: Starting with Gecko 11.0 (Firefox 11.0 / Thunderbird 11.0 / SeaMonkey 2.8), as well as WebKit build 528, these browsers no longer let you use the responseType attribute when performing synchronous requests. Attempting to do so throws an NS_ERROR_DOM_INVALID_ACCESS_ERR exception. This change has been proposed to the W3C for standardization.

So that's great. I'm about to need to change the response type conditionally, but it won't work. It is now my intention to wrap an AJAX asynchronous request in something that will simulate synchronicity.

The following is a generic "make web request" function that my code uses, that I've started adapting to work for my purposes. Unfortunately, it isn't working quite like I'd hoped.

var webResponse = null;

function webCall(action, type, xmlBodyString) {
console.log("In webCall with " + type + ": " + action);
webResponse = null;
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function()
{
    if (xmlhttp.readyState == 4)
    {
        if (xmlhttp.status == 200) {
            webResponse = xmlhttp.responseXML;
        } else {
            var statusTxt = xmlhttp.statusText;
            if (statusTxt == null || statusTxt.length == 0) {
                statusTxt = "An Unknown Error Occurred";
            }
            throw "ERROR " + xmlhttp.status + ":" + statusTxt;
        }
    }
}
xmlhttp.open(type, action, true);
if (xmlBodyString == null) {
    xmlhttp.send();
} else {
    xmlhttp.setRequestHeader("Content-Type", "text/xml");
    xmlhttp.send(xmlBodyString);
}

for (var i = 0; i < 20; i++) {
    if (webResponse != null) {
        break;
    }
    window.setTimeout(nop, 250);
}
if (webResponse == null) {
    throw "Waited 5 seconds for a response, and didn't get one.";
}
console.log("Responding with " + webResponse);
return webResponse;
}

function nop() {
}

So, I thought this was pretty straight-forward. Create a global variable (in retrospect, it probably doesn't even have to be global, but for now, w/e), set up the onreadystatechange to assign a value to it once it's ready, make my asynchronous request, wait a maximum of 5 seconds for the global variable to be not null, and then either return it, or throw an error.

The problem is that my code here doesn't actually wait 5 seconds. Instead, it immediately exits, claiming it waited 5 seconds before doing so.

I made a fiddle, for what it's worth. It doesn't work in there either. http://jsfiddle.net/Z29M5/

Any assistance is greatly appreciated.

Cody S
  • 4,594
  • 7
  • 29
  • 63
  • why do you use synchronous ajax requests? this is not what ajax was made for – lukaleli Oct 09 '13 at 00:17
  • I can't imagine why you would ever *need* to change responseType, rather than merely finding it convenient. A string can always be decoded. – hobbs Oct 09 '13 at 00:18
  • You probably don't need them to be synchronous. Just have them fire one after another using callbacks. – Blender Oct 09 '13 at 00:18
  • You never use `responseType` also `setTimeout` is asynchronous too – Musa Oct 09 '13 at 00:24
  • What in the workld do you think window.setTimeout(nop, 250); does? It does not wait for it to fire to go to the next step! It is not a sleep method. – epascarello Oct 09 '13 at 00:26
  • i've done something like that using showModalDialog(), but man, was it ever ugly! – dandavis Oct 09 '13 at 00:28
  • 1
    @jimmyweb I guess somebody had to say it. Disregarding. – Cody S Oct 09 '13 at 00:47
  • @hobbs If I'm going to use this function to download a file, or, worse yet, encrypted data, I'll need to change it to blob or something presumably. – Cody S Oct 09 '13 at 00:48
  • @Musa I realize that, at this point in my coding, I don't change the responseType, but that is irrelevant. – Cody S Oct 09 '13 at 00:49
  • @epascarello Ok fine. Despite the fact that googling "Javascript sleep" immediately comes up with usage of setTimeout, I'll bite. How do you sleep a javascript function? http://stackoverflow.com/questions/951021/what-do-i-do-if-i-want-a-javascript-version-of-sleep – Cody S Oct 09 '13 at 00:49

3 Answers3

2

You can't do it. Stick to asynchronous requests. Callback hell sucks, but that's what you get in event-driven systems with no language support.

There is simply no way to simulate synchronous code in plain JavaScript in browsers at the moment.

If you could severely limit your supported set of browsers (pretty much just Firefox at the moment AFAIK) you can get synchronous-looking code by using generators.

There are also languages that compile into JS and support synchronous-looking code. One example I can think of (from a few years ago) is this: https://github.com/maxtaco/tamejs

Matti Virkkunen
  • 58,926
  • 7
  • 111
  • 152
  • Callback hell does suck, and that's what I'm trying to avoid. I'm beginning to get the impression that that's what I'm going to be stuck with though. – Cody S Oct 09 '13 at 00:50
1

Firstly, for all the pain, using asynchronous code asynchronously is the way to go. It takes a different approach, that's all.

Secondly, for your specific question, this is what your 'delay' loop is doing:

For twenty iterations
  if we've had a response, break
  set a timeout for 250ms
go round again

(The entire for loop completes all 20 iterations immediately. You won't have a response)

. . . after 250ms

execute the first setTimeout callback, which is nop

execute the second...

I can't think of a quick way to fix this, other than putting your processing code in the AJAX call back, which is where it should be for async code anyway.

0

Why not create an array of requests and just pop them off one-by-one when you get the response from the previous ajax call.

Scary Wombat
  • 41,782
  • 5
  • 32
  • 62