AJAX Streaming
AJAX Streaming
HTTP streaming is not new, but Hazaar provides a structured, reliable way to emit and consume streamed responses without custom buffer management on every request. The framework handles the transport details so you can focus on producing and consuming stream packets.
Why use streaming?
Streaming is particularly useful when:
- Long-running processes - Report progress while a batch job, file upload, or data import is running, keeping users informed instead of showing a spinner for minutes.
- Real-time feedback - Display incremental results from operations like search indexing, report generation, or multi-step workflows.
- Live data feeds - Push server-side events, logs, or monitoring data to the client without polling or WebSockets.
- Progressive rendering - Send parts of a large dataset as they become available, improving perceived performance.
By streaming incremental updates, you can build responsive interfaces that show progress and status in real time without blocking the user or requiring page refreshes.
How it works
There are two parts to making streams work:
- Client initiates the stream from a view using the browser-side stream module.
- Application responds with stream data by calling the
$controller->stream()method.
The framework takes care of the low-level details required for streaming. The sections below show how to wire up both sides.
Client Side
On the client side, import the stream module in a type="module" script, create a stream client with the stream URL, and call start(). Each server-side $controller->stream() call is delivered to the stream.progress() callback as a discrete packet.
Client example
The important requirements are that the stream module is available to the browser, that you pass the stream URL to createStream, and that you register callbacks before starting the stream so you can handle progress updates and completion.
<div id="streamTest"></div>
<script type="module">
import { createStream } from "/hazaar/js/stream.js";
var streamDIV = document.getElementById('streamTest');
var stream = createStream('/stream');
stream.progress(function (data) {
streamDIV.innerHTML += '<p>Received: ' + data + '</p>';
}).start();
</script>The stream client returned by createStream(url, options) supports progress(), done(), error(), and uploadProgress() callbacks, plus abort() to stop the request. Use start() to initiate the stream. If your asset base path differs, update the import to match where /hazaar/js/stream.js is served from in your app.
Server Side
On the server side, the controller calls $this->stream() multiple times to emit packets. Each packet is sent immediately to the client without closing the response.
Stream payloads
$this->stream() accepts strings, integers, floats, arrays, and simple objects (stdClass). The stream encoder preserves the data shape so the client receives the corresponding JavaScript type (string, number, array, or object).
<?php
StreamController extends \Hazaar\Controller\Action {
public function test(){
$this->stream('Starting stream test');
for($i=1; $i<=10; $i++){
$this->stream('i=' . $i);
sleep(1);
}
}
}
?>The example sends an initial message and then emits a value every second. The client receives each message in stream.progress().
Example sending an object:
<?php
StreamController extends \Hazaar\Controller\Action {
public function status(){
$payload = new \stdClass();
$payload->status = 'ok';
$payload->count = 3;
$payload->items = ['alpha', 'beta', 'gamma'];
$this->stream($payload);
}
}
?>Client-side handling for an object payload:
<script type="module">
import { createStream } from "/hazaar/js/stream.js";
var stream = createStream('/stream/status');
stream.progress(function (data) {
if (data && typeof data === 'object') {
console.log('Status:', data.status, 'Count:', data.count, 'Items:', data.items);
}
}).start();
</script>The final packet
Once streaming is activated, the server streams data using $this->stream() as usual. When streaming is complete, execution returns just like any other controller action would, with a Response object. This response can be a JSON response, or as is typical, an array returned from the controller action that is automatically converted into a JSON response.
This final packet is sent to the stream client and is available in the done() callback, allowing you to send completion status, summary data, or final results.
Example with a final packet:
<?php
StreamController extends \Hazaar\Controller\Action {
public function process(){
$this->stream('Starting batch process');
$processed = 0;
for($i=1; $i<=100; $i++){
// Process item
$processed++;
if($i % 10 === 0){
$this->stream("Processed {$processed} items");
}
usleep(100000);
}
// Return final response
return [
'success' => true,
'processed' => $processed,
'duration' => 10.5
];
}
}
?>Client-side handling with done():
<script type="module">
import { createStream } from "/hazaar/js/stream.js";
var stream = createStream('/stream/process');
stream.progress(function (data) {
console.log('Progress:', data);
}).done(function (finalData) {
console.log('Complete! Final result:', finalData);
if (finalData.success) {
alert('Processed ' + finalData.processed + ' items in ' + finalData.duration + ' seconds');
}
}).start();
</script>Error Handling
The stream client provides error() and fail() callbacks to handle errors during streaming. Both callbacks are equivalent (providing jQuery compatibility), so you can use whichever fits your coding style.
Server-side errors
If an exception occurs or an error response is returned during streaming, the error is sent to the client and triggers the error callback. You can send structured error data by throwing exceptions or returning error responses.
<?php
StreamController extends \Hazaar\Controller\Action {
public function riskyProcess(){
try {
$this->stream('Starting risky operation');
// Simulate error condition
if(rand(0, 1)){
throw new \Exception('Something went wrong!');
}
$this->stream('Operation succeeded');
return ['success' => true];
} catch(\Exception $e) {
return Response::error($e->getMessage());
}
}
}
?>Client-side error handling
Use the error() or fail() callback to handle errors. The callback receives the XHR object, error type, and error message.
<script type="module">
import { createStream } from "/hazaar/js/stream.js";
var stream = createStream('/stream/riskyProcess');
stream.progress(function (data) {
console.log('Progress:', data);
}).done(function (finalData) {
console.log('Success:', finalData);
}).error(function (xhr, errorType, errorMessage) {
console.error('Stream error:', errorType, errorMessage);
alert('An error occurred: ' + errorMessage);
}).start();
</script>The error callback handles various failure scenarios including:
- HTTP errors - Non-2xx status codes from the server
- Network errors - Connection failures or timeouts
- Server exceptions - Errors thrown during stream processing
- Aborted requests - Streams cancelled via
abort()