Skip to content

Commit

Permalink
Breaking up complexity of main axios file
Browse files Browse the repository at this point in the history
  • Loading branch information
Matt Zabriskie committed Aug 27, 2014
1 parent 0d0b837 commit 9096d34
Show file tree
Hide file tree
Showing 12 changed files with 1,233 additions and 565 deletions.
378 changes: 254 additions & 124 deletions dist/axios.amd.js

Large diffs are not rendered by default.

340 changes: 235 additions & 105 deletions dist/axios.amd.min.js

Large diffs are not rendered by default.

361 changes: 232 additions & 129 deletions dist/axios.js

Large diffs are not rendered by default.

340 changes: 235 additions & 105 deletions dist/axios.min.js

Large diffs are not rendered by default.

160 changes: 58 additions & 102 deletions lib/axios.js
@@ -1,131 +1,92 @@
var Promise = require('es6-promise').Promise;
var defaults = require('./defaults');
var forEach = require('./forEach');
var merge = require('./merge');
var transformData = require('./transformData');

function axios(options) {
var axios = module.exports = function axios(options) {
options = merge({
method: 'get',
transformRequest: defaults.transformRequest,
transformResponse: defaults.transformResponse
}, options);

var promise = new Promise(function (resolve, reject) {
var request = new(XMLHttpRequest || ActiveXObject)('MSXML2.XMLHTTP.3.0');

function onload() {
if (request.status >= 200 && request.status < 300) {
resolve(transformData(request.responseText, options.headers, options.transformResponse));
} else {
onerror();
}
}

function onerror() {
reject(
parse(request.responseText) ||
new Error('Can\'t connect to ' + JSON.stringify(options.url))
);
}

try {
request.open(options.method, options.url, true);
request.onreadystatechange = function () {
if (request.readyState === 4) {
onload();
var request = new(XMLHttpRequest || ActiveXObject)('Microsoft.XMLHTTP');
var data = transformData(options.data, options.headers, options.transformRequest);

// Open request and listen for ready state
request.open(options.method, options.url, true);
request.onreadystatechange = function () {
if (request && request.readyState === 4) {
// Prepare the response
var response = {
data: transformData(request.responseText, options.headers, options.transformResponse),
status: request.status,
headers: headers,
config: options
};

// Resolve or reject the Promise based on the status
if (request.status >= 200 && request.status < 300) {
resolve(response);
} else {
reject(response);
}
};

request.onload = request.load = onload;
request.onerror = request.error = onerror;

var headers = merge(
defaults.headers.common,
defaults.headers[options.method] || {},
options.headers || {}
);
// Clean up request
request = null;
}
};

for (var key in headers) {
if (headers.hasOwnProperty(key)) {
request.setRequestHeader(key, headers[key]);
}
// Merge headers and add to request
var headers = merge(
defaults.headers.common,
defaults.headers[options.method] || {},
options.headers || {}
);

forEach(headers, function (val, key) {
// Remove Content-Type if data is undefined
if (typeof data === 'undefined' &&
key.toLowerCase() === 'content-type') {
delete headers[key];
}
} catch (e) {
reject(e);
}
// Otherwise add header to the request
else {
request.setRequestHeader(key, val);
}
});

request.send(transformData(options.data, options.headers, options.transformRequest));
// Send the request
request.send(data);
});

promise.success = function (fn) {
// Provide alias for success
promise.success = function success(fn) {
promise.then(function(response) {
fn(response);
});
return promise;
};

promise.error = function(fn) {
// Provide alias for error
promise.error = function error(fn) {
promise.then(null, function(response) {
fn(response);
});
return promise;
};

return promise;
}

var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'};
var defaults = axios.defaults = {
transformRequest: [function (data) {
return data ? JSON.stringify(data) : null;
}],

transformResponse: [function (data) {
return parse(data);
}],

headers: {
common: {'Accept': 'application/json, text/plain, */*'},
patch: merge(CONTENT_TYPE_APPLICATION_JSON),
post: merge(CONTENT_TYPE_APPLICATION_JSON),
put: merge(CONTENT_TYPE_APPLICATION_JSON)
}
};

function transformData(data, headers, fns) {
if (typeof fns === 'function') {
return fns(data, headers);
}

forEach(fns, function (fn) {
data = fn(data, headers);
});

return data;
}

function parse(response) {
try {
return JSON.parse(response);
} catch(e) {
return response;
}
}

function merge() {
var result = {};
forEach(arguments, function (obj) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = obj[key];
}
}
});
return result;
}
// Expose defaults
axios.defaults = defaults;

function forEach(arr, fn) {
for (var i=0, l=arr.length; i<l; i++) {
fn.call(null, arr[i], i, arr);
}
}
// Provide aliases for supported request methods
createShortMethods('delete', 'get', 'head');
createShortMethodsWithData('post', 'put', 'patch');

function createShortMethods() {
forEach(arguments, function (method) {
Expand All @@ -148,9 +109,4 @@ function createShortMethodsWithData() {
}));
};
});
}

createShortMethods('delete', 'get', 'head');
createShortMethodsWithData('post', 'put', 'patch');

module.exports = axios;
}
42 changes: 42 additions & 0 deletions lib/defaults.js
@@ -0,0 +1,42 @@
'use strict';

var merge = require('./merge');

var toString = Object.prototype.toString;
var JSON_START = /^\s*(\[|\{[^\{])/;
var JSON_END = /[\}\]]\s*$/;
var PROTECTION_PREFIX = /^\)\]\}',?\n/;
var CONTENT_TYPE_APPLICATION_JSON = {
'Content-Type': 'application/json;charset=utf-8'
};

module.exports = {
transformRequest: [function (data) {
return data !== null && typeof data === 'object' &&
toString.call(data) !== '[object File]' &&
toString.call(data) !== '[object Blob]' ?
JSON.stringify(data) : null;
}],

transformResponse: [function (data) {
if (typeof data === 'string') {
data = data.replace(PROTECTION_PREFIX, '');
if (JSON_START.test(data) && JSON_END.test(data)) {
data = JSON.parse(data);
}
}
return data;
}],

headers: {
common: {
'Accept': 'application/json, text/plain, */*'
},
patch: merge(CONTENT_TYPE_APPLICATION_JSON),
post: merge(CONTENT_TYPE_APPLICATION_JSON),
put: merge(CONTENT_TYPE_APPLICATION_JSON)
},

xsrfCookiName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN'
};
34 changes: 34 additions & 0 deletions lib/forEach.js
@@ -0,0 +1,34 @@
'use strict';

/**
* Iterate over an Array or an Object invoking a function for each item.
*
* If `obj` is an Array or arguments callback will be called passing
* the value, index, and complete array for each item.
*
* If 'obj' is an Object callback will be called passing
* the value, key, and complete object for each property.
*
* @param {Object|Array} obj The object to iterate
* @param {Function} fn The callback to invoke for each item
*/
module.exports = function forEach(obj, fn) {
if (typeof obj !== 'object') {
return;
}

// Iterate over array values
if (obj.constructor === Array || typeof obj.callee === 'function') {
for (var i=0, l=obj.length; i<l; i++) {
fn.call(null, obj[i], i, obj);
}
}
// Iterate over object keys
else {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
fn.call(null, obj[key], key, obj);
}
}
}
};
28 changes: 28 additions & 0 deletions lib/merge.js
@@ -0,0 +1,28 @@
var forEach = require('./forEach');

/**
* Accepts varargs expecting each argument to be an object, then
* immutably merges the properties of each object and returns result.
*
* When multiple objects contain the same key the later object in
* the arguments list will take precedence.
*
* Example:
*
* ```js
* var result = merge({foo: 123}, {foo: 456});
* console.log(result.foo); // outputs 456
* ```
*
* @param {Object} obj1 Object to merge
* @returns {Object} Result of all merge properties
*/
module.exports = function merge(obj1/*, obj2, obj3, ...*/) {
var result = {};
forEach(arguments, function (obj) {
forEach(obj, function (val, key) {
result[key] = val;
});
});
return result;
};
23 changes: 23 additions & 0 deletions lib/transformData.js
@@ -0,0 +1,23 @@
'use strict';

var forEach = require('./forEach');

/**
* Transform the data for a request or a response
*
* @param {Object|String} data The data to be transformed
* @param {Array} headers The headers for the request or response
* @param {Array|Function} fns A single function or Array of functions
* @returns {*} The resulting transformed data
*/
module.exports = function transformData(data, headers, fns) {
if (typeof fns === 'function') {
return fns(data, headers);
}

forEach(fns, function (fn) {
data = fn(data, headers);
});

return data;
};
33 changes: 33 additions & 0 deletions test/unit/forEach.js
@@ -0,0 +1,33 @@
var forEach = require('../../lib/forEach');

module.exports = {
testArray: function (test) {
var sum = 0;

forEach([1, 2, 3, 4, 5], function (val) {
sum += val;
});

test.equal(sum, 15);
test.done();
},

testObject: function (test) {
var keys = '';
var vals = 0;
var obj = {
b: 1,
a: 2,
r: 3
};

forEach(obj, function (v, k) {
keys += k;
vals += v;
});

test.equal(keys, 'bar');
test.equal(vals, 6);
test.done();
}
};

0 comments on commit 9096d34

Please sign in to comment.