The warning you are getting is because synchronous XMLHttpRequest is generally discouraged: it can hang up the browser if the server does not respond immediately.
If you're only going to run this on localhost
you could disregard the warning if you like, or better, change the synchronous request to asynchronous.
What's making it synchronous is the third parameter to the .open()
call:
rawFile.open("GET", file, false);
To make it asynchronous, change that to true:
rawFile.open("GET", file, true);
Or simply omit the parameter because true
is the default:
rawFile.open("GET", file);
The rest of your code should work as is. (It worked for me in a quick test.) You've already written the code in a way that will work with an asynchronous request: you're using the data inside the onreadystatechange
callback when readyState === 4
, instead of assuming it will be available immediately after you issue the .open()
call as synchronous code might do.
See the MDN documentation for more information.
Update 1
The problem with rules
being undefined outside the onreadystatechange
callback is normal, expected behavior. Remember that we've now changed the request to be asynchronous. That is, the .open()
call initiates the request and then returns immediately, before the data is returned by the server (even a server on localhost
).
The solution to this is simple: do not attempt to access the data inline after making the request. Instead, access it only from within the success callback function, or from another function you call from that callback.
For example, in your updated code, simply move the console.log()
call to the end of the success callback as shown below, or call another function of your own from there if you wish. Whatever else you want to do with the data, follow this same pattern.
Also, don't declare rules
at the top of the code; this will only lead to confusion because the value of rules
is not available until the success callback function has been called (as you discovered). Instead, declare it inside the callback.
Similarly, left
, right1
, right2
, and probability
are only used inside the forEach()
callback, so that is where they should be declared.
Update 2
As we discussed in the comments, regular expressions can be a great way to do this kind of string parsing.
Here is an example that illustrates both a working version of the original code, and a new version that uses regular expressions to parse the rules.
The rules.txt
file I tested with looks like this:
A -> D C : 0.00688
A -> G F : 0.43257
B -> with : 0.1875
C -> with : 0.2
The JavaScript code is:
function readRules( file ) {
var request = new XMLHttpRequest();
request.open( 'GET', file );
request.onreadystatechange = function() {
if( request.readyState === 4 ) {
if( request.status === 200 || request.status == 0 ) {
processRules1( request.responseText );
processRules2( request.responseText );
}
}
}
request.send( null );
}
function processRules1( allText ) {
var rules = {};
allText = allText.split("\n");
allText = allText.splice(0, allText.length-1);
allText.forEach(function (rule) {
left = rule[0];
probability = parseFloat(rule.slice(rule.indexOf(":")+2));
rules[left] ? rules[left] : rules[left] = {}; // init left
if (rule[6] == " ") {
right1 = rule[5];
right2 = rule[7];
rules[left][right1] ? rules[left][right1] : rules[left][right1] = {}; // init right1
rules[left][right1][right2] = probability;
} else {
right1 = rule.slice(5, rule.indexOf(":") - 1);
rules[left][right1] = probability;
}
});
console.log( JSON.stringify( rules, null, 4 ) );
}
function processRules2( rulesText ) {
var reRule = /^(\w) -> (\w+)( (\w))? : ([\d.]+)$/gm;
var rules = {};
rulesText.replace( reRule, function(
line, left, right1, dummy1, right2, probability
) {
if( ! rules[left] ) rules[left] = {};
if( right2 ) {
if( ! rules[left][right1] ) rules[left][right1] = {};
rules[left][right1][right2] = probability;
} else {
rules[left][right1] = probability;
}
});
console.log( JSON.stringify( rules, null, 4 ) );
}
readRules( 'rules.txt' );
processRules1()
uses the original technique, and processRules2()
uses a regular expression.
Both versions log the same result for the test rules.txt
file:
{
"A": {
"D": {
"C": 0.00688
},
"G": {
"F": 0.43257
}
},
"B": {
"with": 0.1875
},
"C": {
"with": 0.2
}
}
The one part of the processRules2()
function that may seem a bit intimidating is the regular expression itself:
var reRule = /^(\w) -> (\w+)( (\w))? : ([\d.]+)$/gm;
Rather than explain it here in detail, let me point you to a page on regex101.com that tests the regular expression and explains how it works. Give me a shout if anything in their explanation isn't clear.
Besides being a bit shorter, the regular expression makes the code much more flexible. For example, suppose the rules file sometimes used more than one space between the values instead of the single space in your existing file. With the original approach, you'd have to recalculate all the character offsets and include code to handle both the single space and multiple spaces. With the regular expression, it would be as simple as changing a
(which matches a single space) to +
(which matches one or more spaces).
Also note that the regular expression takes care of splitting the rules file into lines and ignoring the empty line at the end of the file (instead of using the .splice()
to remove the last line). The empty line simply doesn't match the regular expression so it gets ignored that way.
One odd thing in the code is the use of the .replace()
method. Normally this method is used when you actually want to replace characters in a string with something else. But here we are just using it as a way to call a function on each match in the string, and then we ignore the value returned by .replace()
.
And one last suggestion: In a couple of places the original code is something like this:
rules[left] ? rules[left] : rules[left] = {};
The conditional operator can be handy for similar kinds of uses where you're actually assigning the result to something, but in this particular case it is simpler and more clear to use an ordinary if
statement:
if( ! rules[left] ) rules[left] = {};