Running Asynchronous JavaScript Synchronously
For a recent project, I needed to register a new user. Someone would click a button which would bring up a dialog box. In the dialog box, they would type in their desired user name and click OK or press Enter. The user name needed to be validated in 2 ways - first, they had to match the format (no spaces at the start/end, only valid characters, etc.) and second, they had to be a name that didn't already exist.The best way to check for a name not existing is to run an agent on the server and get a response. In JavaScript, making that call is done via the XMLHttpRequest object. Although that object can be used synchronously, that type of use is getting depricated (for good reason). You don't want to get a user hung up if the server doesn't respond or something. But my code also needs to wait for a response to make sure the name doesn't already exist before proceeding. I came up with a solution that seems to meet all my needs and uses all asynchronous calls.
There are 3 JavaScript functions in play here. The first I'll call "main" which is what is kicked off when the user clicks OK or presses Enter in the input field. This function does the regular expression validation of the format of the name and then kicks off the other two JavaScript functions. It looks something like this:
function main() {
var userName = "";
// Get the user name value off the dialog box field, then do the basic validation of the user name
// (that code is omitted here to save space)
// Kick off the process to check for a duplicate user name
checkForDuplicate(userName);
// Kick off the process to "wait" for the check to finish
setTimeout(function() {
checkForCompletion(1);
}, 500);
}
Kicking off the process to "wait" for the check to finish has to be done through a setTimeout. It HAS to release control. If you put it in a "while" loop, that code continually executes and doesn't allow any other code to process. But when it is placed into a timeout, control is released and it allows the other function to process. The process is kicked off after a 500 millisecond (1/2 second) wait.
Since there are 3 JavaScript functions in play, there are global JavaScript variables (3 in total) which are used to communicate between the functions:
var isCheckComplete = false;
var isDuplicateUserName = false;
var isErrorSituation = false;
The JavaScript function checkForDuplicate makes the call to the server asynchronously. It sets a timeout of 10 seconds - if the server doesn't respond in 10 seconds, then indicate an error situation.
function checkForDuplicate(userName) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var response = xhttp.responseText;
if (response.indexOf("TRUE") != -1) {
isCheckComplete = true;
isDuplicateUserName = true;
} else if (response.indexOf("FALSE") != -1) {
isCheckComplete = true;
isDuplicateUserName = false;
} else {
isCheckComplete = true;
isErrorSituation = true;
}
}
};
xhttp.ontimeout = function () {
isCheckComplete = true;
isErrorSituation = true;
};
var fullURL = webDbName + "/agtCheckForDuplicate?OpenAgent&U=";
var temp = userName;
while (temp.indexOf(" ") != -1) temp = temp.replace(" ", "%20");
fullURL += temp
xhttp.open("GET", fullURL, true);
xhttp.timeout = 10000; // 10 seconds
xhttp.send();
}
Some things to point out with the code. The variable "webDbName" is a global JavaScript variable that is set using @WebDbName. It's a habit I get into when I'm working with JavaScript - just put the database name into a global variable always. The agent "agtCheckForDuplicate" is a LotusScript agent that checks for a duplicate and does a Print statement at the end. It prints either "TRUE" or "FALSE" depending on whether the name was a duplicate ("TRUE") or not a duplicate ("FALSE"). If there's an error during the running of the agent, what will get sent back is the dreaded "Agent Done" message that any Domino web developer has seen. But that's fine for these purposes - the JavaScipt code will know there was an error and set the "isErrorSituation" global variable. If the agent times out (which is highly unlikely, but good to check), it also sets the error situation global variable. Finally, the setting of the timeout property MUST be after the call to the open method. There is an issue in IE11 where if the timeout property is set first, you will get an InvalidStateError error message and the code won't run.
The JavaScript function checkForCompletion looks at the global JavaScript variables to see if they have changed and acts appropriately. If they haven't changed, then the same function is called after another second and the counter is incremented. What's the counter used for? Although the "checkForDuplicate" should time out after 10 seconds, there is a possibility of an error that isn't trapped. For example, if the agent on the server was renamed or if the JavaScript code didn't build the URL correctly. So the counter waits for 15 times (15 seconds, which is longer than the 10 second timeout in the other function). If there's no response after 15 seconds, then assume an error happened.
function checkForCompletion(count) {
if (isCheckComplete) {
if (isErrorSituation) {
// Code to run when there is an error
} else if (isDuplicateUserName) {
// Code to run when the typed in name is a duplicate
} else {
// Code to run when the typed in name is not a duplicate
}
} else {
if (count >= 15) {
// Code to run when there is an error
} else { // still within 15 seconds - check again after another second
setTimeout(function() {
checkForCompletion(count+1);
}, 1000);
}
}
}
Notice how I omitted the code to happen in the 3 situations - error, duplicate user name, not a duplicate user name. I didn't feel those were relevant to the structure of the code. In my situation, I show a HTML <DIV> with some standard error text in the case of an error, and show a different HTML <DIV> in the case of a duplicate. In the case of a non-duplicate, the dialog box is closed and the user name is placed into a hidden field so the user can continue filling out the form.
(Yes, by the way, a "lock" is placed on the user name so someone else can't create the same user name prior to when the first user finishes filling out the form. Again, that's not relevant to the JavaScript structure being described here).
There you have it. JavaScript code that waits for something to happen while still running asynchronously. It's synchronous JavaScript code running asynchronously.