kxios/index.js
import Kxios from './Kxios.js';
import defaultConfig from './defaultConfig.js';
import utils from './utils.js';
function createInstance(config) {
var context = new Kxios(config);
var request = utils.bind(Kxios.prototype.request, context);
utils.extend(request, Kxios.prototype, context);
utils.extend(request, context);
return request;
}
var kxios = createInstance(defaultConfig);
kxios.create = function(config) {
return createInstance(config);
}
export default kxios;
kxios/Kxios.js
import utils from './utils.js';
import InterceptorManager from './InterceptorManager.js';
// var obj1 = {x: 1, y:2, z: {a: 100, b: 200}, arr: [1,2,3]};
// var obj2 = utils.deepCopy(obj1);
// for (var key in obj1) {
// obj2[key] = obj1[key];
// }
// obj2.z.a = 1000;
// console.log(obj1, obj2);
function Kxios(config) {
// 初始化配置
this.config = config;
// 攔截器
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
}
}
Kxios.prototype.request = function(config) {
// 請(qǐng)求需要的配置针饥,把傳入的config與this.config進(jìn)行必要的合并
var config = utils.mergeConfig( this.config, config );
// console.log(config);
/**
* this.interceptors.request
* this.dispatchRequest( config )
* this.interceptors.response
*/
// 調(diào)用鏈
var chain = [this.dispatchRequest, undefined];
// 循環(huán)取出請(qǐng)求攔截器內(nèi)注冊(cè)的所有函數(shù)
/**
* this.interceptors.request.handlers
* => [resove1, reject1, resove2, reject2]
*
* chain = [resove2, reject2, resove1, reject1,this.dispatchRequest, undefined]
*/
var requestHandlers = this.interceptors.request.handlers;
var requestHandlersLen = requestHandlers.length;
for (var i = 0; i < requestHandlersLen; i++) {
chain.unshift( requestHandlers[i].resolve, requestHandlers[i].reject );
}
// 響應(yīng)攔截器是向尾部添加
/**
* this.interceptors.response.handlers
* [resove1, reject1, resove2, reject2]
* chain = [resove2, reject2, resove1, reject1,this.dispatchRequest, undefined,resove1, reject1,resove2, reject2]
*/
var responseHandlers = this.interceptors.response.handlers;
var responseHandlersLen = responseHandlers.length;
for (var i = 0; i < responseHandlersLen; i++) {
chain.push( responseHandlers[i].resolve, responseHandlers[i].reject );
}
/**
* 請(qǐng)求攔截器后注冊(cè)的先執(zhí)行
* 響應(yīng)攔截器先注冊(cè)先執(zhí)行
*/
// 不去調(diào)用chain中的函數(shù)洲守,而是創(chuàng)建一個(gè)新的resolve狀態(tài)的promise
// 作為整個(gè)調(diào)用的起始函數(shù)
var promise = Promise.resolve(config);
// 把chain中的所有函數(shù)添加promise對(duì)象的then中
while (chain.length) {
promise = promise.then( chain.shift(), chain.shift() );
}
// promise.then(f1).then(f2).then(f3)
return promise;
}
Kxios.prototype.dispatchRequest = function(config) {
var adapter = config.adapter;
return adapter(config);
}
// 這4個(gè)請(qǐng)求方式都是不能帶請(qǐng)求體
var method1 = ['delete', 'get', 'head', 'options'];
// method1.forEach( methodName => {
// Kxios.prototype[methodName] = function(url, config) {
// return this.request(utils.deepMerge(config || {}, {
// method: methodName,
// url: url
// }));
// }
// } );
for (var i = 0; i < method1.length; i++) {
(function (i) {
var methodName = method1[i];
Kxios.prototype[methodName] = function (url, config) {
return this.request(utils.deepMerge(config || {}, {
method: methodName,
url: url
}));
}
})(i)
}
// 下面3個(gè)都是可以帶請(qǐng)求體的
var method2 = ['post', 'put', 'patch'];
for (var i = 0; i < method2.length; i++) {
(function (i) {
var methodName = method2[i];
Kxios.prototype[methodName] = function (url, data, config) {
return this.request(utils.deepMerge(config || {}, {
method: methodName,
url: url,
data: data
}));
}
})(i)
}
export default Kxios;
kxios/defaultConfig.js
import xhrAdapter from './xhrAdapter.js';
var defaultsConfig = {
// `url` is the server URL that will be used for the request
url: '',
// `method` is the request method to be used when making the request
method: 'get', // default
// `baseURL` will be prepended to `url` unless `url` is absolute.
// It can be convenient to set `baseURL` for an instance of axios to pass relative URLs
// to methods of that instance.
baseURL: '',
// `transformRequest` allows changes to the request data before it is sent to the server
// This is only applicable for request methods 'PUT', 'POST', 'PATCH' and 'DELETE'
// The last function in the array must return a string or an instance of Buffer, ArrayBuffer,
// FormData or Stream
// You may modify the headers object.
transformRequest: [function (data, headers) {
// Do whatever you want to transform the data
return data;
}],
// `transformResponse` allows changes to the response data to be made before
// it is passed to then/catch
transformResponse: [function (data) {
// Do whatever you want to transform the data
if (typeof data === 'string') {
try {
data = JSON.parse(data);
} catch (e) {}
}
return data;
}],
// `headers` are custom headers to be sent
headers: {
'X-Requested-With': 'XMLHttpRequest'
},
// `params` are the URL parameters to be sent with the request
// Must be a plain object or a URLSearchParams object
params: {},
// `paramsSerializer` is an optional function in charge of serializing `params`
// (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
paramsSerializer: function (params) {
return params
},
// `data` is the data to be sent as the request body
// Only applicable for request methods 'PUT', 'POST', and 'PATCH'
// When no `transformRequest` is set, must be of one of the following types:
// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
// - Browser only: FormData, File, Blob
// - Node only: Stream, Buffer
data: {},
// `timeout` specifies the number of milliseconds before the request times out.
// If the request takes longer than `timeout`, the request will be aborted.
timeout: 1000, // default is `0` (no timeout)
// `withCredentials` indicates whether or not cross-site Access-Control requests
// should be made using credentials
withCredentials: false, // default
// `adapter` allows custom handling of requests which makes testing easier.
// Return a promise and supply a valid response (see lib/adapters/README.md).
adapter: function (config) {
// return (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') ? httpAdapter : xhrAdapter;
return typeof window === 'object' ? xhrAdapter(config) : httpAdapter(config);
},
// `auth` indicates that HTTP Basic auth should be used, and supplies credentials.
// This will set an `Authorization` header, overwriting any existing
// `Authorization` custom headers you have set using `headers`.
// Please note that only HTTP Basic auth is configurable through this parameter.
// For Bearer tokens and such, use `Authorization` custom headers instead.
auth: {
username: '',
password: ''
},
// `responseType` indicates the type of data that the server will respond with
// options are: 'arraybuffer', 'document', 'json', 'text', 'stream'
// browser only: 'blob'
responseType: 'json', // default
// `responseEncoding` indicates encoding to use for decoding responses
// Note: Ignored for `responseType` of 'stream' or client-side requests
responseEncoding: 'utf8', // default
// `xsrfCookieName` is the name of the cookie to use as a value for xsrf token
xsrfCookieName: 'XSRF-TOKEN', // default
// `xsrfHeaderName` is the name of the http header that carries the xsrf token value
xsrfHeaderName: 'X-XSRF-TOKEN', // default
// `onUploadProgress` allows handling of progress events for uploads
onUploadProgress: function (progressEvent) {
// Do whatever you want with the native progress event
},
// `onDownloadProgress` allows handling of progress events for downloads
onDownloadProgress: function (progressEvent) {
// Do whatever you want with the native progress event
},
// `maxContentLength` defines the max size of the http response content in bytes allowed
maxContentLength: 2000,
// `validateStatus` defines whether to resolve or reject the promise for a given
// HTTP response status code. If `validateStatus` returns `true` (or is set to `null`
// or `undefined`), the promise will be resolved; otherwise, the promise will be
// rejected.
validateStatus: function (status) {
return status >= 200 && status < 300; // default
},
// `maxRedirects` defines the maximum number of redirects to follow in node.js.
// If set to 0, no redirects will be followed.
maxRedirects: 5, // default
// `socketPath` defines a UNIX Socket to be used in node.js.
// e.g. '/var/run/docker.sock' to send requests to the docker daemon.
// Only either `socketPath` or `proxy` can be specified.
// If both are specified, `socketPath` is used.
socketPath: null, // default
// `httpAgent` and `httpsAgent` define a custom agent to be used when performing http
// and https requests, respectively, in node.js. This allows options to be added like
// `keepAlive` that are not enabled by default.
httpAgent: {
keepAlive: true
},
httpsAgent: {
keepAlive: true
},
// 'proxy' defines the hostname and port of the proxy server.
// You can also define your proxy using the conventional `http_proxy` and
// `https_proxy` environment variables. If you are using environment variables
// for your proxy configuration, you can also define a `no_proxy` environment
// variable as a comma-separated list of domains that should not be proxied.
// Use `false` to disable proxies, ignoring environment variables.
// `auth` indicates that HTTP Basic auth should be used to connect to the proxy, and
// supplies credentials.
// This will set an `Proxy-Authorization` header, overwriting any existing
// `Proxy-Authorization` custom headers you have set using `headers`.
proxy: {
host: '127.0.0.1',
port: 9000,
auth: {
username: '',
password: ''
}
},
// `cancelToken` specifies a cancel token that can be used to cancel the request
// (see Cancellation section below for details)
cancelToken: ''
};
export default defaultsConfig;
kxios/InterceptorManager.js
function InterceptorManager() {
this.handlers = [];
}
InterceptorManager.prototype.use = function ( resolve, reject ) {
this.handlers.push({
resolve: resolve,
reject: reject
});
}
export default InterceptorManager;
kxios/xhrAdapter.js
export default function(config) {
return new Promise(function(resolve, reject) {
console.info('%c [adapter] : XMLHttpRequest', 'color:blue;font-size:20px;');
var xhr = new XMLHttpRequest();
xhr.onload = function() {
// console.log(this);
var responseData = {
data: JSON.parse(this.responseText),
status: this.status,
statusText: this.statusText
}
resolve( responseData );
}
xhr.open( config.method , config.url, true);
xhr.send();
})
}
kxios/fetchAdapter.js
export default function(config) {
return new Promise(function(resolve, reject) {// 實(shí)際可以不需要這一層Promise,這里為了測(cè)試輸出
console.info('%c [adapter] : Fetch', 'color:blue;font-size:20px;');
var responseData = {};
return fetch(config.url, {
method: config.method
}).then(function(res) {
responseData = {
status: res.status,
statusText: res.statusText
};
return res.json();
}, function(err) {
reject(err);
}).then(function(data) {
responseData.data = data;
resolve(responseData);
});
});
}
kxios/utils.js
function isArray(val) {
return Object.prototype.toString.call(val) === '[object Array]';
}
function isObject(val) {
return typeof val === 'object' && val !== null;
}
function deepCopy(obj2) {
/**
* 把一個(gè)對(duì)象遞歸拷貝給另外一個(gè)對(duì)象
* 源對(duì)象與拷貝后的對(duì)象沒有引用關(guān)系
*/
var obj = isArray(obj2) ? [] : {};
for (var property in obj2) {
// 如果當(dāng)前拷貝的數(shù)據(jù)還是一個(gè)對(duì)象的話眷射,那么繼續(xù)調(diào)用
// deepCopy 進(jìn)行二次拷貝
// 遞歸
if (isObject(obj2[property])) {
obj[property] = deepCopy(obj2[property]);
} else {
obj[property] = obj2[property];
}
}
return obj;
}
function deepMerge(obj1, obj2) {
var obj = deepCopy(obj1);
for (var property in obj2) {
var val = obj[property];
var val2 = obj2[property];
if ( isObject(val) && isObject(val2)) {
obj[property] = deepMerge(val, val2);
} else if (isObject(val2)) {
obj[property] = deepCopy(val2);
} else {
obj[property] = val2;
}
}
return obj;
}
function mergeConfig(config1, config2) {
/**
* 注意:
* 不能直接把config2合并到config1中
*
* 1. 把config1先通過深拷貝賦值給一個(gè)新的對(duì)象
*/
var config = deepCopy(config1);
var properties1 = ['url', 'method', 'params', 'data'];
// 把config2合并到config
for (var property in config2) {
// 針對(duì)一些沒有必要合并的配置直接賦值
if ( properties1.indexOf(property) != -1 ) {
config[property] = config2[property];
} else {
if (isObject(config2[property])) {
// merge
config[property] = deepMerge(config[property], config2[property]);
} else {
config[property] = config2[property];
}
}
}
return config;
}
function bind(fn, context) {
return function () {
var args = [];
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
return fn.apply(context, args);
}
}
function extend(obj1, obj2, context) {
for (var property in obj2) {
if (typeof obj2[property] === 'funtion') {
obj1[property] = bind(obj2[property], context);
} else {
obj1[property] = obj2[property];
}
}
}
export default {
isArray,
isObject,
deepCopy,
deepMerge,
mergeConfig,
bind,
extend
};
最后來使用這個(gè)手工寫好的axios
// import Kxios from './kxios/Kxios.js';
// var kxios = new Kxios({
// // baseUrl: 'http://localhost:7777',
// // headers: {
// // x: 1
// // }
// });
// console.log( kxios );
// kxios.interceptors.request.use(function(config) {
// // console.log(config);
// console.log('request1');
// return config;
// });
// kxios.interceptors.request.use(function(config) {
// // console.log(config);
// console.log('request2');
// // return new Promise(function(resolve) {
// // setTimeout(function() {
// // resolve();
// // }, 3000);
// // })
// return config;
// });
// kxios.interceptors.response.use(function(res) {
// // console.log(res);
// console.log('response1');
// return res;
// });
// kxios.interceptors.response.use(function(res) {
// // console.log(res);
// console.log('response2');
// // if (res.data.code != 0) {
// // alert();
// // }
// return res;
// });
// kxios.request({
// method: 'get',
// url: 'http://localhost:7777/data'
// }).then( function(res) {
// console.log(res);
// } );
// kxios.request({
// method: 'get',
// url: '/data'
// });
// Promise.resolve(123).then(function(v) {
// console.log(v);
// return v;
// }).then(function(v) {
// console.log(v);
// })
// kxios.get('http://localhost:7777/data').then(function(res) {
// console.log(res);
// });
import kxios from './kxios/index.js';
import fetchAdapter from './kxios/fetchAdapter.js';
import xhrAdapter from './kxios/xhrAdapter.js';
// import jsonpAdapter from './kxios/jsonpAdapter.js';
// console.dir(kxios.config);
kxios.config.adapter = function(config) {
return xhrAdapter(config);
}
kxios.get('http://localhost:7777/data').then(function(res) {
console.log(res);
});
// kxios({
// url: 'http://localhost:7777/data'
// }).then(function(res) {
// console.log(res);
// });
// let kxios2 = kxios.create({
// baseURL: 'http://www2.com'
// });
// kxios2.get();