1

I'm trying to return a value from an onreadystatechange AJAX call... I found this page : stackoverflow link. I though I had it working but realised that it made no difference to add or remove the fn function. The following code works :

username_is_available();

function username_is_available() {
  var username = document.getElementById('username').value;

  get_data('username', username, function(returned_value) {
   if (returned_value == 'true') {
     document.getElementById('username_err').innerHTML = 'Taken';
    } else {
     document.getElementById('username_err').innerHTML = 'Available';
    };
  });
}

function get_data(data_type, data, fn) {
  var xmlhttp = new XMLHttpRequest();
  xmlhttp.onreadystatechange = function() {
  if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
    fn(xmlhttp.responseText);
  }
};
  xmlhttp.open("GET", "availability.php?" + data_type + "=" + data, true);
  xmlhttp.send();
}

It all works fine but that's not my goal, I would like a function username_is_available() that returns true if the username is indeed available. Instead, here I an action happens (innerHTML is changed). And if I try and do a return in the anonymous function I get the same result as if I had returned it from directly inside the onreadystatechange : var unasigned

Ivan
  • 34,531
  • 8
  • 55
  • 100

2 Answers2

5

Unfortunately, since the process to determine if a username is taken is asynchronous, there is no way to simply return a value of true or false from the function call. What you can do is set up something similar to what you have now (callbacks) using language features specifically designed for this exact purpose.

A Promise is one of these features.

Usage would look something roughly like this:

function username_is_available(username) {
    return new Promise(resolve => {
        get_data("username", username, resolve);
    });
}

function get_data(data_type, data, fn) {
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function() {
        if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
            fn(xmlhttp.responseText == "true");
        }
    };
    xmlhttp.open("GET", "availability.php?" + data_type + "=" + data, true);
    xmlhttp.send();
}

// Usage:
username_is_available("Ivan").then(available => {
    let text = available ? "Available" : "Taken";
    document.getElementById("username_err").innerHTML = text;
});

This relies on availablity.php returning true and false as text, which is converted to a Boolean before resolve is called.

In the future, when ES7+ async and await directives are available, using the promise will be as simple as this (note the await keyword):

let available = await username_is_available("Ivan");
let text = available ? "Available" : "Taken";
document.getElementById("username_err").innerHTML = text;

Edit: If you can't use ES6 or promises, it's back to good ol' callbacks!

function username_is_available(username, callback) {
    get_data("username", username, callback);
}

function get_data(data_type, data, fn) {
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function() {
        if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
            fn(xmlhttp.responseText == "true");
        }
    };
    xmlhttp.open("GET", "availability.php?" + data_type + "=" + data, true);
    xmlhttp.send();
}

// Usage:
username_is_available("Ivan", function(available) {
    var text = available ? "Available" : "Taken";
    document.getElementById("username_err").innerHTML = text;
});
Scott
  • 5,338
  • 5
  • 45
  • 70
  • 1
    In your last example, I think `taken` should be `available`. – Patrick Roberts May 13 '16 at 19:24
  • You assign a value to `taken` and use an undeclared variable `available`. The variable name `taken` should be `available` because it is receiving a boolean from a function with the phrase `is_available` in it. – Patrick Roberts May 13 '16 at 19:28
  • @PatrickRoberts Good catch, I was caught in the first-answer buzz, frantically writing code in the text box rather than a proper editor and pasting as I usually do. Thanks! – Scott May 13 '16 at 19:29
  • I think it's worth mentioning this uses ES6 features. [Browser compatibility for Promises](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise#Browser_compatibility) isn't very good yet, so he would need a polyfill or library like [Bluebird](https://github.com/petkaantonov/bluebird) – Shaun May 13 '16 at 20:01
  • @Shaun Good point, I updated my answer to include a standard callback version. Thank you! – Scott May 13 '16 at 20:06
  • 1
    Thanks Scott, I tried it but it always outputs 'Available'. The problem doesn't come from the php file since it worked with my other js code. Do you know what causes it to not work ? I noticed you missed a }; on your snippet, to close the xmlhttp.onreadystatechange. – Ivan May 13 '16 at 20:12
  • @Ivan Haha, this is what happens when I don't test things. Fixed. – Scott May 13 '16 at 20:18
  • @ScottKaye your callbackversion is it different from mine ? – Ivan May 13 '16 at 20:52
  • If I can't return a boolean from this king of query, what's the point of using Promises ? Is it the same thing ? – Ivan May 14 '16 at 13:42
  • @Ivan Like I said, it's impossible to return values from asynchronous operations. The JS runtime itself is single-threaded, meaning if it was able to "wait" for async tasks to complete, the browser would periodically freeze. You might be able to use ES6 generators to accomplish what you're looking for besides Promises, but it will still be more involved than simply returning a value. Handling async is why Promises exist, because it's such a huge part of how JS works, unfortunately. – Scott May 14 '16 at 17:11
  • @ScottKaye Ok, i found a [video](https://www.youtube.com/watch?v=dfJTFNp0ZEc) where a guy makes something similar but with JQuery, does it uses promises to pull it off ? Also you said : "If you can't use ES6 or promises" does that mean some servers can't use it or is it about browser versions ? I have an idea for a solution : use a promise, send a AJAX call to fire the php file, then when the promise is fulfilled, check the .textContent of the username_err to see if it's blank or not, and return true or false accordingly. Does that make sense ? – Ivan May 16 '16 at 16:39
  • @Ivan That video is from 2012, so it's likely not using promises. Your idea makes sense to me, though. ES6 is a version/featureset of JavaScript that [some browsers don't support](https://kangax.github.io/compat-table/es6/). Please open a new thread if you have more questions though, this comment thread is getting pretty unwieldy :) – Scott May 16 '16 at 16:59
  • You're right, I created a new [thread here](http://stackoverflow.com/questions/37227564/checking-form-fields-with-ajax-and-php) – Ivan May 16 '16 at 17:27
0

I've been playing around with observers as an alternative to promises. I set up a an example in plnkr to show how it can work. I think in your case it would look like this:

function Producer() {
    this.listeners = [];
}

Producer.prototype.add = function(listener) {
    this.listeners.push(listener);
};

Producer.prototype.remove = function(listener) {
    var index = this.listeners.indexOf(listener);
    this.listeners.splice(index, 1);
};

Producer.prototype.notify = function(message) {
    this.listeners.forEach(function(listener) {
        listener.update(message);
    });
};

var notifier = new Producer;

function get_data(data_type, data) {
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
            notifier.notify(xmlhttp.responseText);
        }
    };
    xmlhttp.open("GET", "availability.php?" + data_type + "=" + data, true);
    xmlhttp.send();
}

var username_is_available = {
    update: function(returned_value) {
        var username = document.getElementById('username').value;
        if (returned_value == 'true') {
            document.getElementById('username_err').innerHTML = 'Taken';
        } else {
            document.getElementById('username_err').innerHTML = 'Available';
        };        
    }
}

notifier.add(username_is_available);
get_data("username", username);

Note the Producer code is reusable, you would make a new instance for other ajax/observable requests.

Shaun
  • 2,012
  • 20
  • 44