Cody S
Cody S

Reputation: 4824

Javascript: Using asynchronous AJAX synchronously

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.

Upvotes: 0

Views: 3463

Answers (3)

Scary Wombat
Scary Wombat

Reputation: 44834

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.

Upvotes: 0

user1864610
user1864610

Reputation:

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.

Upvotes: 1

Matti Virkkunen
Matti Virkkunen

Reputation: 65126

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

Upvotes: 2

Related Questions