mojax
Version:
extendable ajax request module
337 lines (288 loc) • 11.1 kB
JavaScript
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.ajax = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
;
var xhrFactory = require("./lib/xhrFactory"),
chainAddons = require("./lib/chainAddons"),
finalizeParams = require("./lib/finalizeParams");
/**
* @summary creates a mojax instance, which can be extended with middleware/addons, and from which HTTP requests can be dispatched
* @returns {requester}
*/
function createRequester() {
var addons = [];
/**
* @typedef {object} requester
* @summary environment/interface used to add middleware and make HTTP requests
*/
return {
/**
* @function
* @name requester#use
*
* @summary adds a middleware function to the requester pipeline
* @param {function} fn - middleware function to add to the requester's request pipeline
* @returns {object} this
*/
use: function(fn) {
if (typeof fn === "function") {
addons.push(fn);
} else {
throw new TypeError("ajax.use() expects a function argument. Provided:\n" + fn + " (" + typeof fn + ")");
}
return this;
},
/**
* @function
* @name requester#req
*
* @summary starts an async HTTP request, passing the configuration object through the middleware pipeline, and finally sending the request
* @param {object} params - configuration object used for the request
* @returns {object} this
*/
req: function(params) {
xhrFactory(
chainAddons(addons.concat(finalizeParams), params)
);
return this;
}
};
}
module.exports = {
createRequester: createRequester
};
},{"./lib/chainAddons":2,"./lib/finalizeParams":3,"./lib/xhrFactory":7}],2:[function(require,module,exports){
module.exports = function chainAddons(addons, params) {
"use strict";
return addons.reduce(function(config, nextAddon) {
if (Object.prototype.toString.call(config) === "[object Object]") {
return nextAddon(config);
}
}, params);
};
},{}],3:[function(require,module,exports){
var mapCallbacks = require("./mapCallbacks");
/**
* @summary enforces required properties of the request configuration object.
* @param {object} c - request configuration object
*/
function finalizeParams(c) {
"use strict";
if (Object.prototype.toString.call(c) !== "[object Object]") {
throw new TypeError("ajax request expects an object argument. Provided\n" + c + " (" + typeof c + ")");
}
// url must be string
if (typeof c.url !== "string") {
throw new TypeError("ajax request object expects prop 'url' to be a string. Provided\n" + c.url + " (" + typeof c.url + ")");
}
// timeout can be set
if (c.timeout && typeof c.timeout !== "number") {
throw new TypeError("ajax request object timeout property must be an integer. Provided\n" + c.timeout + " (" + typeof c.timeout + ")");
}
// request method must be uppercase string === GET | POST | PUT | DELETE
if (typeof c.method === "string") {
// enforce proper case
c.method = c.method.toUpperCase();
if (["GET","POST","PUT","DELETE"].indexOf(c.method) === -1) {
throw new RangeError("ajax: 'method' must have one of the following values:\nGET\nPOST\nPUT\nDELETE\nProvided: " + c.method);
}
} else {
throw new RangeError("ajax request object expects property 'method' to be one of:\n GET, POST, PUT, DELETE\n Provided:\n" + c.method);
}
// optional
if (c.responseType) {
// enforce proper case
c.responseType = c.responseType.toLowerCase();
if (["json","text","xml","text/xml","text/html","arraybuffer","blob"].indexOf(c.responseType) === -1) {
throw new RangeError("ajax: responseType must have one of the following values:\narraybuffer\nblob\njson\ntext\nxml\ntext/xml\ntext/html\n\nProvided: " + c.responseType);
}
} else {
c.responseType = "text";
}
// force mandatory X-Requested-With header
if (c.headers) {
if (Object.prototype.toString.call(c.headers) !== "[object Object]") {
throw new TypeError("ajax request object expects prop 'headers' to be an object of key:value pairing for header:value\nProvided:\n" + c.headers);
} else {
Object.keys(c.headers).forEach(function(key) {
if (typeof c.headers[key] !== "string") {
throw new TypeError("ajax request object 'headers' properties must all be strings.\nProvided " + typeof c.headers[key] + " for " + key);
}
});
if (!c.headers["X-Requested-With"]) {
c.headers["X-Requested-With"] = "XMLHttpRequest";
}
}
} else {
c.headers = {
"X-Requested-With": "XMLHttpRequest"
};
}
if (c.method === "POST" || c.method === "PUT") {
if (typeof c.data !== "string") {
throw new TypeError("ajax request object prop 'data' must be present on POST or PUT requests and must be a string. Provided\n" + c.data + " (" + typeof c.data + ")");
}
}
["onOpen", "onHeaders", "onSuccess", "onFailure"].forEach(function(key) {
c[key] = mapCallbacks(c[key]);
});
return c;
}
module.exports = finalizeParams;
},{"./mapCallbacks":4}],4:[function(require,module,exports){
/**
* Maps a function to an array of functions, otherwise returns an empty array.
* @param {function|function[]|undefined|null} v - initial value to map to an array
* @returns {function[]|array} - array of functions or empty array
*/
module.exports = function mapCallbacks(v) {
"use strict";
if (typeof v === "function") {
return [v];
}
if (Array.isArray(v)) {
v.forEach(function(fn) {
if (typeof fn !== "function") {
throw new TypeError("expected function or array of functions for callbacks. Provided\n" + fn + " (" + typeof fn + ")");
}
});
return v;
}
if (v === null || v === undefined) {
return [];
} else {
throw new TypeError("expected function or array of functions for callbacks. Provided\n" + v + " (" + typeof v + ")");
}
};
},{}],5:[function(require,module,exports){
/**
* @author jQuery
* @copyright jQuery foundation
* @license https://github.com/jquery/jquery/blob/master/LICENSE.txt
*
* Parses XML string content
* @param {string} data - content
* @returns {xml}
*/
function parseXML(data) {
"use strict";
var xml;
// Support: IE9
try {
xml = (new window.DOMParser()).parseFromString(data, "text/xml");
} catch (e) {
xml = undefined;
}
if (!xml || xml.getElementsByTagName("parsererror").length) {
throw new SyntaxError("");
}
return xml;
}
/**
* @memberof ajax
* @summary parses the response data based on provided responseType
* @private
* @param xhr {object} XMLHttpRequest object
* @returns {object|string}
*/
module.exports = function parseResponse(xhr) {
"use strict";
// only try to parse if not already parsed by xhr
if (typeof xhr.response === "string" && xhr.response !== "") {
if (xhr.responseType === "json") {
return JSON.parse(xhr.response);
} else if (xhr.type === "text/xml" || xhr.type === "xml") {
return parseXML(xhr.response);
}
}
return xhr.response;
};
},{}],6:[function(require,module,exports){
var parseResponse = require("./parseResponse");
/**
* @summary triggers existing listeners based on state of XMLHttpRequest.
* @param xhr
* @param {object} c - configuration object
*/
module.exports = function(xhr, c) {
"use strict";
var resp;
switch (xhr.readyState) {
case 1: {
if (c.onOpen.length) {
c.onOpen.forEach(function(cb) {
cb(xhr);
});
}
break;
}
case 2: {
if (c.onHeaders.length) {
c.onHeaders.forEach(function(cb) {
cb(xhr);
});
}
break;
}
case 4: {
// dispatch to callbacks
if (c.onSuccess.length && /2|3/.test(xhr.status.toString().charAt(0))) {
try {
resp = parseResponse(xhr.response, c.responseType);
} catch (e) {
if (e instanceof SyntaxError) {
throw new SyntaxError("unable to parse response of type: " + c.responseType + " for\n" + xhr.response);
} else {
throw Error(e);
}
}
// SUCCESS
c.onSuccess.forEach(function(cb) {
cb(xhr, resp);
});
} else if (c.onFailure.length && /4|5/.test(xhr.status.toString().charAt(0))) {
// FAILURE
c.onFailure.forEach(function(cb) {
cb(xhr);
});
}
break;
}
default: {
break;
}
}
};
},{"./parseResponse":5}],7:[function(require,module,exports){
var responseListener = require("./responseListener");
/**
* @memberof ajax
* @summary executes the XMLHttpRequest
* @private
* @param c {object} - request configuration hash
*/
module.exports = function xhrFactory(c) {
"use strict";
var xhr = new XMLHttpRequest();
// early abort, if config invalid
if (Object.prototype.toString.call(c) !== "[object Object]") {
return;
}
// bind listeners
xhr.onreadystatechange = function() {
responseListener(xhr, c);
};
// start XMLHttpRequest
xhr.open(c.method, c.url, true);
// set request headers
// must be done after open()
Object.keys(c.headers || {}).forEach(function(h) {
xhr.setRequestHeader(h, c.headers[h]);
});
// handle timeout
if (c.timeout) {
xhr.timeout = c.timeout;
}
xhr.send(c.data);
return xhr;
};
},{"./responseListener":6}]},{},[1])(1)
});