I have read this question but it doesn't exactly answer my question.
Unfortunately, it looks like things have changed in in the XHR object since I last looked at AJAX, so it is no longer possible to directly access responseText
before it is finished being populated.
I have to write a page that uses AJAX (preferably jQuery, but I am open to suggestions) to retrieve CSV data via HTTP from a server I have no control over. The response data could be quite large; a megabyte of text is not uncommon.
The server is stream-friendly. Is there still any way to get access to a stream of data as it is being returned, directly from JavaScript?
I do have the option of writing some PHP code that lives in the middle and uses some sort of "Comet" tech (long-polling, EventSource, etc), but I would prefer to avoid that if possible.
In case it is relevant, assume for this question that users have the latest version of Firefox/Chrome/Opera and old browser compatibility is not an issue.
This is quite straightforward when outputting text or HTML. Below is an example.
(You'll run into issues if trying to output JSON however, which I'll tackle further down.)
header('Content-type: text/html; charset=utf-8');
function output($val)
{
echo $val;
flush();
ob_flush();
usleep(500000);
}
output('Begin... (counting to 10)');
for( $i = 0 ; $i < 10 ; $i++ )
{
output($i+1);
}
output('End...');
<!DOCTYPE>
<html>
<head>
<title>Flushed ajax test</title>
<meta charset="UTF-8" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
</head>
<body>
<script type="text/javascript">
var last_response_len = false;
$.ajax('./flushed-ajax.php', {
xhrFields: {
onprogress: function(e)
{
var this_response, response = e.currentTarget.response;
if(last_response_len === false)
{
this_response = response;
last_response_len = response.length;
}
else
{
this_response = response.substring(last_response_len);
last_response_len = response.length;
}
console.log(this_response);
}
}
})
.done(function(data)
{
console.log('Complete response = ' + data);
})
.fail(function(data)
{
console.log('Error: ', data);
});
console.log('Request Sent');
</script>
</body>
</html>
It's not actually possible to load a single JSON object incrementally (before it's fully loaded) because until you have the complete object, the syntax will always be invalid.
But if your response has multiple JSON objects, one after another, then it's possible to load one at a time, as they come down the pipe.
So I tweaked my code above by...
Changing PHP FILE line 4 from echo $val;
to echo '{"name":"'.$val.'"};'
. This outputs a series of JSON objects.
Changing HTML FILE line 24 from console.log(this_response);
to
this_response = JSON.parse(this_response);
console.log(this_response.name);
Note that this rudimentary code assumes that each "chunk" coming to the browser is a valid JSON object. This will not always be the case because you cannot predict how packets will arrive - you may need to split the string based on semi-colons (or come up with another separator character).
application/json
Do NOT For change your headers to application/json
- I did this and it had me Googling for 3 days. When the response type is application/json
, the browser waits until the response is complete, as in fully complete. The full response is then parsed to check if it is infact JSON. However our FULL response is {...};{...};{...};
which is NOT valid JSON. The jqXHR.done
method assumes there was an error, because the complete response cannot be parsed as JSON.
As mentioned in the comments, you can disable this check on the client side by using:
$.ajax(..., {dataType: "text"})
Hope some people find this useful.