( function( global, factory ) {
"use strict";
if ( typeof module === "object" && typeof module.exports === "object" ) {
module.exports = global.document ?
factory( global, true ) :
function( w ) {
if ( !w.document ) {
throw new Error( "zhr requires a window with a document" );
}
return factory( w );
};
} else {
factory( global );
}
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
"use strict";
const methodsArr = ['get', 'head', 'post', 'put', 'delete', 'connect', 'options', 'trace', 'patch'];
const empty = (value) => {
if (null === value) {
return true;
} else if (['Set', 'Map'].includes(Object.prototype.toString.call(value).slice(8, -1))) {
return value.size == 0 ? true : false;
} else if ("object" == typeof value) {
if (Object.getOwnPropertyDescriptor(value, "length")) {
return value.length > 0 ? false : true;
} else {
return Object.keys(value).length > 0 ? false : true;
}
} else if (value == 0) {
return true;
} else {
return !value;
}
}
const varType = (any) => {
return Object.prototype.toString.call(any).slice(8, -1).toLowerCase();
}
const trimBase = (str, charlist, type = 0) => {
if (typeof str !== 'string') {
return str;
}
if (typeof charlist === 'undefined') {
if (type === 1) {
return str.replace(/^\s+/gm, '');
} else if (type === 2) {
return str.replace(/\s+$/gm, '');
} else {
return str.replace(/^\s+|\s+$/gm, '');
}
}
charlist = charlist.toString().split('');
let zy = ['$', '(', ')', '*', '+', '.', '[', ']', '?', '\\', '/', '^', '{', '}', '!', '|'],
ps = '';
for (let item of charlist) {
if (zy.includes(item)) {
ps += '\\';
}
ps += item;
}
let reg;
if (type === 1) {
reg = new RegExp("\^" + ps + "+", "gm");
} else if (type === 2) {
reg = new RegExp(ps + "+$", "gm");
} else {
reg = new RegExp("\^" + ps + "+|" + ps + "+$", "gm");
}
return str.replace(reg, '');
}
const utils = {
extend(a, b, context) {
for (let key in b) {
if (b.hasOwnProperty(key)) {
if (typeof b[key] === 'function') {
a[key] = b[key].bind(context);
} else {
a[key] = b[key]
}
}
}
},
transformRequestParam(param, urlEncode = false) {
let dataType = varType(param),
result;
switch (dataType) {
case 'array': {
result = [];
Object.values(param).forEach((item, index) => {
switch (varType(item)) {
case 'string':
result.push(item);
break;
case 'array':
item[1] = empty(item[1]) ? '' : item[1];
!empty(item[0]) && result.push(item[0] + '=' + encodeURIComponent(item[1]));
break;
case 'object':
item.value = empty(item.value) ? '' : item.value;
!empty(item.key) && result.push(item.key + '=' + encodeURIComponent(item.value));
break;
}
})
result = result.join('&');
}
break;
case 'object': {
result = [];
Object.entries(param).forEach(([key, item = ''], index) => {
result.push(key + '=' + encodeURIComponent(item));
})
result = result.join('&');
break;
}
default:
result = (urlEncode && varType(result) !== 'string') ? '' : param;
}
return result;
},
trim(str, charlist) {
return trimBase(str, charlist, 0);
}
,
ltrim(str, charlist) {
return trimBase(str, charlist, 1);
}
,
rtrim(str, charlist) {
return trimBase(str, charlist, 2);
}
,
isNum(value) {
return value !== "" && value !== null && !isNaN(value);
}
}
class InterceptorsManage {
constructor() {
this.handlers = [];
}
use(fullfield, rejected) {
this.handlers.push({
fullfield,
rejected
})
}
}
const sendXhr = (config) => {
return new Promise((resolve, reject) => {
let timeoutId = null, headers;
if (varType(config.headers) !== 'object') {
headers = {}
}
let url = varType(config.url) === 'string' ? config.url : '';
let method = (varType(config.method) === 'string' &&
(config.method = config.method.toLowerCase()) &&
methodsArr.includes(config.method)) ? config.method : 'GET';
method = method.toUpperCase();
if (!empty(config.body)) {
config.body = utils.transformRequestParam(config.body, method === 'GET');
}
switch (varType(config.body)) {
case 'formdata':
headers['content-type'] = 'application/multipart/form-data';
break;
case 'object':
config.body = JSON.stringify(config.body);
headers['content-type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
break;
default:
try {
JSON.parse(config.body);
headers['content-type'] = 'application/json; charset=UTF-8';
} catch (e) {
headers['content-type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
}
}
let query = '';
if (!empty(config.query)) {
query = utils.transformRequestParam(config.query, true);
}
if (['GET', 'DELETE', 'HEAD', 'OPTIONS'].includes(method) && !empty(config.body) && varType(config.body) === 'string') {
query = utils.trim(query + '&' + config.body, '&');
}
if (!empty(query)) {
url = url.indexOf('?') > 0 ? utils.trim(url, '&') + '&' + query : url + '?' + query;
}
let requestConfig = {
url,
method,
headers,
cache: config.cache,
credentials: 'include',
}
if (!['GET', 'DELETE', 'HEAD', 'OPTIONS'].includes(method)) {
requestConfig.body = config.body;
}
if (varType(config.xhrFields) === 'object') {
Object.entries(config.xhrFields).forEach(([key, item], index) => {
if (key === 'headers') {
if (varType(item) === 'object') {
Object.entries(item).forEach(([idx, val], ii) => {
requestConfig.headers[idx] = val;
})
}
} else if (!['url', 'method'].includes(key)) {
requestConfig[key] = item;
}
})
}
if (config.timeout != 0 && config.timeout !== false) {
const controller = new AbortController()
if (utils.isNum(config.timeout)) {
config.timeout = parseInt(config.timeout)
}
else {
config.timeout = 60000;
}
timeoutId = setTimeout(() => controller.abort(), config.timeout)
requestConfig.signal = controller.signal;
}
fetch(url, requestConfig).then(response => {
let responseType = null,
responseHeadersArray = [],
responseHeaders = {},
responsePromise;
response.headers.forEach((item, key) => {
responseHeadersArray.push({
key,
item
});
responseHeaders[key] = item;
})
if (varType(config.responseType) === 'string' && ['json', 'text', 'blob', 'arraybuffer'].includes(config.responseType.toLowerCase())) {
responseType = config.responseType.toLowerCase();
} else {
responseType = response.headers.get('content-type');
if (responseType.includes('/json')) {
responseType = 'json';
} else if (responseType.includes('text/html')) {
responseType = 'text';
}
}
switch (responseType) {
case 'json':
responsePromise = response.json()
break;
case 'blob':
responsePromise = response.blob()
break;
case 'arraybuffer':
responsePromise = response.arrayBuffer()
break;
default:
responsePromise = response.text()
}
responsePromise.then(result => {
clearTimeout(timeoutId)
response.$result = result;
response.$config = requestConfig;
response.$responseHeaders = responseHeaders;
response.$responseHeadersArray = responseHeadersArray;
resolve(response)
}).catch(reject);
}).catch(reject)
})
}
class Zhr {
constructor() {
this.interceptors = {
request: new InterceptorsManage,
response: new InterceptorsManage
}
}
request(config) {
let chain = [sendXhr.bind(this), undefined]
this.interceptors.request.handlers.forEach(interceptor => {
chain.unshift(interceptor.fullfield, interceptor.rejected)
})
this.interceptors.response.handlers.forEach(interceptor => {
chain.push(interceptor.fullfield, interceptor.rejected)
})
let promise = Promise.resolve(config);
while (chain.length > 0) {
promise = promise.then(chain.shift(), chain.shift())
}
return promise;
}
}
methodsArr.forEach(met => {
Zhr.prototype[met] = function() {
return this.request({
method: met,
url: arguments[0],
body: arguments[1] || {},
...arguments[2] || {}
})
}
})
function createZhrFn() {
let zhr = new Zhr();
let req = zhr.request.bind(zhr);
utils.extend(req, Zhr.prototype, zhr)
utils.extend(req, zhr)
return req;
}
const zhr = createZhrFn();
var
_zhr = window.zhr,
_zsl = window.zsl;
zhr.noConflict = function( deep ) {
if ( window.zsl === zhr ) {
window.zsl = _zsl;
}
if ( deep && window.zhr === zhr ) {
window.zhr = _zhr;
}
return zhr;
};
if ( typeof noGlobal === "undefined" ) {
window.zhr = window.zsl = zhr;
}
return zhr;
});
拦截器使用
发送请求案例
zhr({
url: 'http://127.0.0.1',
timeout: 10000,
query: [
{key: 'k1', value: 'v1'},
['k2', 'v2'],
'k3=v3'
],
body: {
},
method: 'post'
}).then(response => {
console.log(response.$result)
}).catch(err => console.log(err))