google-closure-library
Version:
Google's common JavaScript library
259 lines (216 loc) • 5.99 kB
JavaScript
/**
* @license
* Copyright The Closure Library Authors.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview adaptor of XhrStreamReader to the NodeReadableStream interface.
*/
goog.module('goog.net.streams.xhrNodeReadableStream');
goog.module.declareLegacyNamespace();
const NodeReadableStream = goog.require('goog.net.streams.NodeReadableStream');
const googArray = goog.require('goog.array');
const googLog = goog.require('goog.log');
const {XhrStreamReader, XhrStreamReaderStatus} = goog.require('goog.net.streams.xhrStreamReader');
/**
* The XhrNodeReadableStream class.
*
* @implements {NodeReadableStream}
* @struct
* @final
* @package
*/
class XhrNodeReadableStream {
/**
* @param {!XhrStreamReader} xhrReader The XhrStreamReader object that handles
* the events of the underlying Xhr.
*/
constructor(xhrReader) {
/**
* @const
* @private {?googLog.Logger} the logger.
*/
this.logger_ = googLog.getLogger('goog.net.streams.XhrNodeReadableStream');
/**
* The xhr reader.
*
* @private {!XhrStreamReader} the xhr reader.
*/
this.xhrReader_ = xhrReader;
this.xhrReader_.setDataHandler(goog.bind(this.onData_, this));
this.xhrReader_.setStatusHandler(goog.bind(this.onStatusChange_, this));
/**
* The callback map, keyed by eventTypes.
*
* @private {!Object<!Array<function(!Object=)>>}
*/
this.callbackMap_ = {};
/**
* The callback-once map, keyed by eventTypes.
*
* @private {!Object<!Array<function(!Object=)>>}
*/
this.callbackOnceMap_ = {};
}
/**
* @override
* @param {string} eventType
* @param {function(!Object=)} callback
* @return {!NodeReadableStream}
*/
on(eventType, callback) {
let callbacks = this.callbackMap_[eventType];
if (!callbacks) {
callbacks = [];
this.callbackMap_[eventType] = callbacks;
}
callbacks.push(callback);
return this;
}
/**
* @override
* @param {string} eventType
* @param {function(!Object=)} callback
* @return {!NodeReadableStream}
*/
addListener(eventType, callback) {
this.on(eventType, callback);
return this;
}
/**
* @override
* @param {string} eventType
* @param {function(!Object=)} callback
* @return {!NodeReadableStream}
*/
removeListener(eventType, callback) {
const callbacks = this.callbackMap_[eventType];
if (callbacks) {
googArray.remove(callbacks, callback); // keep the empty array
}
const onceCallbacks = this.callbackOnceMap_[eventType];
if (onceCallbacks) {
googArray.remove(onceCallbacks, callback);
}
return this;
}
/**
* @override
* @param {string} eventType
* @param {function(!Object=)} callback
* @return {!NodeReadableStream}
*/
once(eventType, callback) {
let callbacks = this.callbackOnceMap_[eventType];
if (!callbacks) {
callbacks = [];
this.callbackOnceMap_[eventType] = callbacks;
}
callbacks.push(callback);
return this;
}
/**
* Handles any new data from XHR.
*
* @param {!Array<!Object>} messages New messages, to be delivered in order
* and atomically.
* @private
*/
onData_(messages) {
const callbacks = this.callbackMap_[NodeReadableStream.EventType.DATA];
if (callbacks) {
this.doMessages_(messages, callbacks);
}
const onceCallbacks =
this.callbackOnceMap_[NodeReadableStream.EventType.DATA];
if (onceCallbacks) {
this.doMessages_(messages, onceCallbacks);
}
this.callbackOnceMap_[NodeReadableStream.EventType.DATA] = [];
}
/**
* Deliver messages to registered callbacks.
*
* Exceptions are caught and logged (debug), and ignored otherwise.
*
* @param {!Array<!Object>} messages The messages to be delivered
* @param {!Array<function(!Object=)>} callbacks The callbacks.
* @private
*/
doMessages_(messages, callbacks) {
const self = this;
for (let i = 0; i < messages.length; i++) {
const message = messages[i];
callbacks.forEach(function(callback) {
try {
callback(message);
} catch (ex) {
self.handleError_('message-callback exception (ignored) ' + ex);
}
});
}
}
/**
* Handles any state changes from XHR.
*
* @private
*/
onStatusChange_() {
const currentStatus = this.xhrReader_.getStatus();
const EventType = NodeReadableStream.EventType;
switch (currentStatus) {
case XhrStreamReaderStatus.ACTIVE:
this.doStatus_(EventType.READABLE);
break;
case XhrStreamReaderStatus.BAD_DATA:
case XhrStreamReaderStatus.HANDLER_EXCEPTION:
case XhrStreamReaderStatus.NO_DATA:
case XhrStreamReaderStatus.TIMEOUT:
case XhrStreamReaderStatus.XHR_ERROR:
this.doStatus_(EventType.ERROR);
break;
case XhrStreamReaderStatus.CANCELLED:
this.doStatus_(EventType.CLOSE);
break;
case XhrStreamReaderStatus.SUCCESS:
this.doStatus_(EventType.END);
break;
}
}
/**
* Run status change callbacks.
*
* @param {string} eventType The event type
* @private
*/
doStatus_(eventType) {
const callbacks = this.callbackMap_[eventType];
const self = this;
if (callbacks) {
callbacks.forEach(function(callback) {
try {
callback();
} catch (ex) {
self.handleError_('status-callback exception (ignored) ' + ex);
}
});
}
const onceCallbacks = this.callbackOnceMap_[eventType];
if (onceCallbacks) {
onceCallbacks.forEach(function(callback) {
callback();
});
}
this.callbackOnceMap_[eventType] = [];
}
/**
* Log an error
*
* @param {string} message The error message
* @private
*/
handleError_(message) {
googLog.error(this.logger_, message);
}
}
exports = {XhrNodeReadableStream};