views:

500

answers:

1

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?

A: 

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, and while (prevDataLength !== xhr.responseText.length) { ... } loop is not needed at all.

According to William's answer to Are mutexes needed in JavaScript? StackOverflow question:

Javascript is defined as a reentrant language which means there is no threading exposed to the user, there may be threads in the implementation. Functions like setTimeout() and asynchronous callbacks need to wait for the script engine to sleep before they're able to run.

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:

The DOM event model is reentrant. Event listeners may perform actions that cause additional events to be dispatched. Such events are handled in a synchronous manner, the event propagation that causes the event listener to be triggered will resume only after the event dispatch of the new event is completed.

That means that everything that happens in an event must be finished before the next event will be processed.


Second, global variables prevDataLength and nextReadPos 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.

Jakub Narębski