How in JavaScript process unhandled yet part of XMLHttpRequest responseText only in onprogress/onreadystatechange handler, without using global variables for XMLHttpRequest, prevDataLength, nextreadPos and inProgress (for locking)?
The problem is the following: I want to update web page based on currently received part of data obtained using AJAX (current value of responseText of XMLHttprequest). I want to update page as soon as server sends data (it is so called HTTP streaming pattern). When I do update, I want to process only yet unhandled part of responseText.
Additional complication is that responseText may contain unfinished fragment of response; one can easily deal with that (like I do) by using terminators; then you can easily extract complete responses.
Currently I use global variables and locking to avoid problem where handler is called while earlier call didn't finished working (processing new data). My code looks (simplified) like the following:
function processData(unprocessed, nextReadPos) {
var lastLineEnd = unprocessed.lastIndexOf('\n');
if (lastLineEnd !== -1) {
var lines = unprocessed.substring(0, lastLineEnd).split('\n');
nextReadPos += lastLineEnd + 1 /* 1 == '\n'.length */;
processLines(lines);
} // end if
return nextReadPos;
}
function handleResponse() {
...
// here xhr.readyState === 4 && xhr.status === 200
// in case we were called before finished processing
if (inProgress) {
return;
} else {
inProgress = true;
}
// extract new whole (complete) lines, and process them
while (prevDataLength !== xhr.responseText.length) {
if (xhr.readyState === 4 &&
prevDataLength === xhr.responseText.length) {
break;
}
prevDataLength = xhr.responseText.length;
var unprocessed = xhr.responseText.substring(nextReadPos);
nextReadPos = processData(unprocessed, nextReadPos);
} // end while
...
inProgress = false;
}
...
xhr.onreadystatechange = handleResponse;
If I have simpler handler I could protect against re-entering handler when it didn't finish work by passing XMLHttpRequest object as parameter, like e.g. case 1 in "Step 3 – A Simple Example" section of AJAX/Getting_Started article at Mozilla Developer Centre:
httpRequest.onreadystatechange = function() { alertContents(httpRequest); }; //1 (simultaneous request)
which in notation used in larger fragment of code is
xhr.onreadystatechange = function() { handleResponse(xhr); }; //1 (simultaneous request)
But that example doesn't tell me what to do with other global variables: prevDataLength
and nextReadPos
. They are used to decide if there is any new data, and to extract complete 'sentences' from server response, respectively.
How to do that without global variables?
I am answering my own question, because there were no other answers
First, the global variable
inProgress
, used as semaphor / mutex to protect critical section, andwhile (prevDataLength !== xhr.responseText.length) { ... }
loop is not needed at all.According to William's answer to Are mutexes needed in JavaScript? StackOverflow question:
This agrees with description in "Document Object Model (DOM) Level 3 Events W3C Specification", chapter 1. "Document Object Model Events", section 1.2 "Event dispatch and DOM event flow", last paragraph:
That means that everything that happens in an event must be finished before the next event will be processed.
Second, global variables
prevDataLength
andnextReadPos
could (and probably should) be added as additional extra properties to XmlHttpRequest object.Alternate solution would be to use closures to have private / static variables, as described somewhat in Private Members in JavaScript by Douglas Crockford, and in Example 3: Encapsulating Related Functionality in comp.lang.javascript FAQ notes :: Javascript Closures.