Skip to content

Instantly share code, notes, and snippets.

@chrisguitarguy
Last active November 27, 2015 02:37
Show Gist options
  • Save chrisguitarguy/b1a5681b2e29e6619323 to your computer and use it in GitHub Desktop.
Save chrisguitarguy/b1a5681b2e29e6619323 to your computer and use it in GitHub Desktop.
Turns out it's pretty simple to write a little promised-based ajax wrapper
var assign = require('object-assign');
var Promise = require('es6-promise').Promise;
var EventEmitter = require('eventemitter2').EventEmitter2;
function buildQueryString(object, prefix) {
var out = [];
for (var prop in object) {
if (!object.hasOwnProperty(prop)) {
continue;
}
var k = prefix ? prefix+'['+prop+']' : prop;
var v = object[prop];
out.push(typeof v in 'object' ?
buildQueryString(v, k) :
encodeURIComponent(k)+'='+encodeURIComponent(v));
}
return out.join('&');
}
function appendQueryString(url, qsAsObject) {
if (!qsAsObject || typeof qsAsObject !== 'object') {
return url;
}
url += '?'+buildQueryString(qsAsObject);
return url;
}
function makeResponse(xhr) {
return Object.create(Object.prototype, {
status: {
configurable: false,
enumerable: true,
writeable: false,
value: xhr.status
},
statusText: {
configurable: false,
enumerable: true,
writeable: false,
value: xhr.statusText
},
type: {
configurable: false,
enumerable: true,
writeable: false,
value: xhr.responseType
},
body: {
configurable: false,
enumerable: true,
writeable: false,
value: xhr.response
}
});
}
function ServerError(resp, xhr) {
Error.apply(this);
this.message = resp.statusText;
this.response = resp;
this.xhr = xhr;
this.name = 'ajax.ServerError';
}
ServerError.prototype = Object.create(Error.prototype);
function NetworkError(xhr) {
Error.apply(this);
this.name = 'ajax.NetworkError';
this.message = 'Network Error';
this.xhr = xhr;
}
NetworkError.prototype = Object.create(Error.prototype);
function readyStateChangeHandler(xhr, resolve, reject) {
if (xhr.readyState !== XMLHttpRequest.DONE) {
return;
}
var response = makeResponse(xhr);
this.emit('request:received', xhr, response)
if (xhr.status >= 200 && xhr.status < 300) {
this.emit('request:succeeded', xhr, response);
resolve(response);
} else {
var err = new ServerError(response, xhr);
this.emit('request:failed', xhr, err);
reject(err);
}
}
function errorHandler(xhr, reject) {
var err = new NetworkError(xhr);
this.emit('request:errored', xhr, err);
reject(err);
}
function Ajax(config) {
EventEmitter.call(this, {
wildcard: true,
delimiter: ':'
});
config || (config = {});
this._beforeSend = config.beforeSend || function (xhr) { };
};
function _methodRequest(method) {
return function (url, data, config) {
config || (config = {});
config.data = data;
return this.request(method, url, config);
}
}
Ajax.prototype = assign({}, EventEmitter.prototype, {
request: function (method, url, config) {
console.log(this);
config || (config = {});
method = method.toUpperCase();
var body = undefined;
switch (method) {
case 'GET':
case 'HEAD':
url = appendQueryString(url, config.data);
break;
case 'POST':
case 'PUT':
case 'PATCH':
case 'DELETE':
body = config.data;
break;
default:
throw new Error(method+' requests are not supported')
}
var xhr = this._createRequest(method, url, config);
return this._createPromise(xhr, body);
},
get: _methodRequest('GET'),
head: _methodRequest('HEAD'),
post: _methodRequest('POST'),
put: _methodRequest('PUT'),
patch: _methodRequest('PATCH'),
// delete
destroy: _methodRequest('DELETE'),
_createRequest: function (method, url, config) {
var xhr = new XMLHttpRequest();
var headers = config.headers || {};
xhr.open(method, url);
xhr.responseType = "json";
(config.beforeSend || this._beforeSend).call(this, xhr, config);
for (var h in headers) {
xhr.setRequestHeader(h, headers[h]);
}
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
xhr.setRequestHeader('Accept', 'application/json');
return xhr;
},
_createPromise: function (xhr, body) {
return new Promise(function (resolve, reject) {
xhr.onreadystatechange = readyStateChangeHandler.bind(this, xhr, resolve, reject);
xhr.onerror = errorHandler.bind(this, xhr, reject);
this.emit('request:sending', xhr, body);
xhr.send(body);
this.emit('request:sent', xhr, body);
}.bind(this));
}
});
module.exports = {
Ajax: Ajax,
ServerError: ServerError,
NetworkError: NetworkError
};
// make the api accesible via a "default" object
var defaultAjax = new Ajax();
var methods = [
'request',
'get',
'head',
'post',
'put',
'patch',
'destroy',
'once',
'many',
'on',
'onAny',
'off',
'offAny',
'removeAllListeners',
'listeners',
'listenersAny'
];
for (var i in methods) {
var m = methods[i];
module.exports[m] = defaultAjax[m].bind(defaultAjax);
}
{
"name": "chrisguitarguy-ajax",
"description": "Ajax library to play around with promises, etc",
"dependencies": {
"object-assign": "~4.0",
"es6-promise": "~3.0",
"eventemitter2": "~0.4"
},
"devDependencies": {
"browserify": "~12.0",
"babelify": "~7.2",
"babel-preset-es2015": "~6.1",
"vinyl-source-stream": "~1.1",
"gulp": "~3.9"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment