Skip to content

Instantly share code, notes, and snippets.

@mjpitz
Created January 26, 2016 04:03
Show Gist options
  • Save mjpitz/12cc6e5154539ed1c782 to your computer and use it in GitHub Desktop.
Save mjpitz/12cc6e5154539ed1c782 to your computer and use it in GitHub Desktop.
Kinda a cool idea where I applied the Fluent Interface design pattern at the XmlHttpRequest object with some additional helpers.
// provide our own undefined
const undefined = void(0);
// provide an extend method
Object.extend = function(protoTo, ...protoFroms) {
protoFroms.forEach(function(protoFrom) {
Object.keys(protoFrom).forEach(function(key) {
if (protoTo[key] === undefined)
protoTo[key] = protoFrom[key];
});
});
};
// provide a global setImmediate function
const setImmediate = function(func, ...args) {
setTimeout(function() {
func.apply(null, args);
}, 0);
};
const newXhrRequest = (function(...factories) {
var cachedIndex;
for (int i = 0; i < factories.length; i++) {
try {
factories[i]();
cachedIndex = i;
break;
} catch (e) {/* do nothing */}
}
if (cachedIndex === undefined)
throw new Error('Cannot determine XMLHttpRequest interface');
return factories[cachedIndex];
})(
function() {
return new XMLHttpRequest();
},
function() {
return new ActiveXObject("Msxml3.XMLHTTP");
},
function() {
return new ActiveXObject("Msxml2.XMLHTTP.6.0");
},
function() {
return new ActiveXObject("Msxml2.XMLHTTP.3.0");
},
function() {
return new ActiveXObject("Msxml2.XMLHTTP");
},
function() {
return new ActiveXObject("Microsoft.XMLHTTP");
}
);
const VALID_EVENTS = [
'opened', 'sent', 'headers', 'complete', 'success', 'redirect', 'failure', 'timeout', 'abort'
];
/**
* [FluentXhr description]
* @constructor
*/
var FluentXhr = function() {
var httpRequest = newXhrRequest();
/**
* @type {XMLHttpRequest}
*/
this.httpRequest_ = httpRequest;
/**
* @type {Object}
*/
this.handlers_ = {};
/**
* @type {Object}
*/
this.dispatched_ = {};
// populate with placeholder
VALID_EVENTS.forEach(function(evnt) {
this.handlers_[evnt] = [];
this.dispatched_[evnt] = undefined;
}, this);
var self = this;
httpRequest.onreadystatechange = function() {
switch (httpRequest.readyState) {
case XMLHttpRequest.UNSENT:
this.dispatch_('opened', {});
break;
case XMLHttpRequest.OPENED:
this.dispatch_('sent', {});
break;
case XMLHttpRequest.HEADERS_RECEIVED:
var headers = {};
var headerString = httpRequest.getAllResponseHeaders();
headerString.split('\r\n').forEach(function(line) {
if (line.indexOf('HTTP/') != 0) {
var [key, value] = line.split(': ');
if (headers[key] && Array.isArray(headers[key])) {
headers[key].push(value);
} else if (headers[key]) {
var tmp = headers[key];
headers[key] = [tmp, value];
} else {
headers[key] = value;
}
}
});
this.dispatch_('headers', headers);
break;
case XMLHttpRequest.LOADING:
break;
case XMLHttpRequest.DONE:
var payload = {
response: httpRequest.response,
text: httpRequest.responseText,
type: httpRequest.responseType,
xml: httpRequest.responseXML,
status: httpRequest.status,
series: Math.floor(httpRequest.status / 100),
statusText: httpRequest.statusText
};
this.dispatch_('complete', payload);
if (payload.series < 3) {
this.dispatch_('success', payload);
} else if (payload.series > 3) {
this.dispatch_('failure', payload);
} else {
this.dispatch_('redirect', payload);
}
break;
}
}.bind(this);
httpRequest.ontimeout = function() {
this.dispatch_('timeout', {});
}.bind(this);
};
/**
* Provide familiar methods for working with an xhr object
*/
Object.extend(FluentXhr.prototype, {
/**
*/
abort: function() {
this.httpRequest_.abort();
this.dispatch_('abort', {});
},
/**
* @param {String} method
* @param {String} url
* @param {boolean=} opt_async
* @param {String?=} opt_user
* @param {String?=} opt_password
* @return {FluentXhr}
*/
open: function(method, url, opt_async, opt_user, opt_password) {
this.httpRequest_.open(method, url, opt_async, opt_user, opt_password);
return this;
},
/**
* @param {String} mime
* @return {FluentXhr}
*/
overrideMimeType: function(mime) {
this.httpRequest_.overrideMimeType(mime)
return this;
},
/**
* @param {String} header
* @param {String} value
* @return {FluentXhr}
*/
setRequestHeader: function(header, value) {
this.httpRequest_.setRequestHeader(header, value);
return this;
},
/**
* @param {boolean} withCredentials
* @return {FluentXhr}
*/
withCredentials: function(withCredentials) {
this.httpRequest_.withCredentials = withCredentials;
return this;
},
/**
* @param {number} timeout
* @return {FluentXhr}
*/
timeout: function(timeout) {
this.httpRequest_.timeout = timeout;
return this;
},
/**
* @param {ArrayBuffer|ArrayBufferView|Blob|Document|String|FormData=} opt_data [description]
* @return {FluentXhr}
*/
send: function(opt_data) {
this.httpRequest_.send(opt_data);
return this;
}
});
/**
* Provide the core event emmission methods.
*/
Object.extend(FluentXhr.prototype, {
/**
* @param {String} key
* @param {*} payload
*/
dispatch_: function(key, payload) {
this.dispatched_[key] = payload;
this.handlers_[key].forEach(function(func) {
setImmediate(func, payload);
});
},
/**
* @param {String} key
* @param {Array.<Function>} funcs
* @return {FluentXhr}
*/
on: function(key, ...funcs) {
// add to the handlers
funcs.forEach(function(func) {
this.handlers_[key].push(func);
}, this);
// enlighten immediately
var payload = this.dispatched_[key];
if (payload) {
funcs.forEach(function(func) {
setImmediate(func, payload);
}, this);
}
return this;
}
});
/**
* Provide more fluent methods for interacting with the xhr response.
*/
Object.extend(FluentXhr.prototype, {
/**
* @param {Array.<Function>} funcs
* @return {FluentXhr}
*/
onOpened: function(...funcs) {
return this.on('opened', funcs);
},
/**
* @param {Array.<Function>} funcs
* @return {FluentXhr}
*/
onSent: function(...funcs) {
return this.on('sent', funcs);
},
/**
* @param {Array.<Function>} funcs
* @return {FluentXhr}
*/
onHeaders: function(...funcs) {
return this.on('headers', funcs);
},
/**
* @param {Array.<Function>} funcs
* @return {FluentXhr}
*/
onComplete: function(...funcs) {
return this.on('complete', funcs);
},
/**
* @param {Array.<Function>} funcs
* @return {FluentXhr}
*/
onSuccess: function(...funcs) {
return this.on('success', funcs);
},
/**
* @param {Array.<Function>} funcs
* @return {FluentXhr}
*/
onRedirect: function(...funcs) {
return this.on('redirect', funcs);
},
/**
* @param {Array.<Function>} funcs
* @return {FluentXhr}
*/
onFailure: function(...funcs) {
return this.on('failure', funcs);
},
/**
* @param {Array.<Function>} funcs
* @return {FluentXhr}
*/
onTimeout: function(...funcs) {
return this.on('timeout', funcs);
},
/**
* @param {Array.<Function>} funcs
* @return {FluentXhr}
*/
onAbort: function(...funcs) {
return this.on('abort', funcs);
}
});
// export
module.exports = exports = FluentXhr;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment