/*
* 基于Promise封装一个ajax库
* + 参考axios的语法
* + XMLHttpRequest
*
* 基于语法:
* ajax([config])
* ajax.get/head/options/delete([url],[config])
* ajax.post/put([url],[data],[config])
*
* 二次配置:
* + 默认配置项
* + ajax.defaults.xxx 修改默认配置项
* + ajax([config]) 自己传递的配置项
*
* {
* baseURL:'',
* url:'',
* method:'get',
* transformRequest:function(data){return data;}, 请求主体传递信息的格式化
* headers:{
* 'Content-Type':'application/json'
* },
* params:{}, URL传参数信息(拼接到URL的末尾)
* cache:true, GET系列请求中是否清除缓存(?_=随机数)
* data:{}, 请求主体传参信息
* timeout:0, 设置请求超时时间
* withCredentials:false, 跨域请求中允许携带资源凭证
* responseType:'json', 预设服务器返回结果的处理方案 'stream', 'document', 'json', 'text'
* validateStatus: function (status) {
* return status >= 200 && status < 300; // default
* }
* }
*
* 拦截器:
* + 请求拦截器 ajax.interceptors.request(function(config){})
* + 响应拦截器 ajax.interceptors.response(function(response){},function(reason){})
* + ajax.all([promise array])
*
* 基于ajax请求回来的结果都是promise实例
* + response
* + data 响应主体信息
* + status 状态码
* + statusText 状态码的描述
* + headers 响应头信息
* + request XHR原生对象
* + reason
* + response
* + message
* + ...
*/
(function () {
// 核心库的封装
function ajax(config = {}) {
return new init(config);
}
ajax.prototype = {
constructor: ajax,
version: "0.0.1",
// 发送ajax请求
send() {
let {
method,
timeout,
withCredentials,
headers,
validateStatus,
adapter2,
} = this.config;
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open(method, this.initURL());
// 基础配置
xhr.timeout = timeout;
xhr.withCredentials = withCredentials;
Object.keys(headers).forEach(key => {
xhr.setRequestHeader(key, headers[key]);
});
xhr.onreadystatechange = () => {
let status = xhr.status,
state = xhr.readyState;
if (!validateStatus(status)) {
// 状态码代表失败(有请求结果)
reject(this.initResult(false, xhr));
return;
}
// 成功
if (state === 4) {
resolve(this.initResult(true, xhr));
}
};
xhr.onerror = err => {
// 请求都是失败的
reject({
message: err.message,
});
};
xhr.send(this.initData());
}).then(
// 响应拦截器:在返回的promise实例和自己调用then中拦截一道
...adapter2
);
},
// 初始化URL
initURL() {
let { url, baseURL, params, cache } = this.config;
url = baseURL + url;
// 问号参数
params = ajax.stringify(params);
if (params) {
url += `${url.includes("?") ? "&" : "?"}${params}`;
}
// 缓存处理
if (!cache) {
url += `${url.includes("?") ? "&" : "?"}_=${+new Date()}`;
}
return url;
},
// 请求主体信息处理 (只对POST有效)
initData() {
let { method, data, transformRequest } = this.config;
if (this.GET_REG.test(method)) return null;
return transformRequest(data);
},
// 构建成功失败的返回结果
getHeaders(xhr) {
let headersText = xhr.getAllResponseHeaders(),
obj = {};
headersText = headersText.split(/(?:\n)/g);
headersText.forEach(item => {
if (!item) return;
let [key, value] = item.split(": ");
obj[key] = value;
});
return obj;
},
initResult(flag, xhr) {
let { responseType } = this.config;
let response = {
data: {},
request: xhr,
status: xhr.status,
statusText: xhr.statusText,
headers: this.getHeaders(xhr),
};
if (flag) {
// 成功
let res = xhr.responseText;
switch (responseType.toLowerCase()) {
case "json":
res = JSON.parse(res);
break;
case "document":
res = xhr.responseXML;
break;
case "stream":
res = xhr.response;
break;
}
response.data = res;
return response;
}
// 失败
return {
response,
message: xhr.statusText,
};
},
};
function init(config) {
// 自己传递的配置项替换默认配置项
Object.keys(config).forEach(key => {
ajax.defaults[key] = config[key];
});
// 请求拦截器:处理config
defaults = defaults.adapter1[0](defaults);
// 把配置项挂在到实例上
this.config = defaults;
this.GET_REG = /^(GET|HEAD|DELETE|OPTIONS)$/i;
// 发送请求,但是最后返回的是一个promise实例
return this.send();
}
init.prototype = ajax.prototype;
// 默认配置项(基于Proxy劫持数据,控制只允许ajax.defaults.xxx=xxx来修改配置项)
let defaultsType = {
baseURL: "string",
url: "string",
method: "string",
params: "object",
cache: "boolean",
data: "object",
timeout: "number",
withCredentials: "boolean",
responseType: "string",
headers: "object",
transformRequest: "function",
validateStatus: "function",
adapter1: "array",
adapter2: "array",
};
let defaults = {
baseURL: "",
url: "",
method: "get",
params: {},
cache: true,
data: {},
timeout: 0,
withCredentials: false,
responseType: "json",
headers: {
"Content-Type": "application/json",
},
transformRequest: data => JSON.stringify(data),
validateStatus: status => status >= 200 && status < 300,
// 拦截器参数 请求/响应
adapter1: [
function (config) {
return config;
},
],
adapter2: [
function (response) {
return Promise.resolve(response);
},
function (reason) {
return Promise.reject(reason);
},
],
};
ajax.defaults = new Proxy(defaults, {
set(obj, prop, value) {
// 拦截器不走这个配置
if (prop === "adapter1" || prop === "adapter2") {
return;
}
// 格式是否合法的处理
let defT = defaultsType[prop] || "string",
valT = value === null ? "null" : typeof value;
if (defT !== valT) {
throw new TypeError(`传参格式错误:${prop}参数的格式必须是${defT}!`);
}
// 和之前的HEADERS信息合并后再处理
if (prop === "headers") {
value = Object.assign(defaults.headers, value);
}
obj[prop] = value;
},
});
// 拦截器的处理
ajax.interceptors = {
request(callback) {
if (typeof callback !== "function") {
throw new TypeError(`传参格式错误:实参的格式必须是函数!`);
}
defaults.adapter1 = [callback];
},
response(success, error) {
// null和undefined不管,其余的处理
if (success != undefined) {
if (typeof success !== "function") {
throw new TypeError(`传参格式错误:实参的格式必须是函数!`);
}
defaults.adapter2[0] = success;
}
if (error != undefined) {
if (typeof error !== "function") {
throw new TypeError(`传参格式错误:实参的格式必须是函数!`);
}
defaults.adapter2[1] = error;
}
},
};
// 快捷调用的办法(最后本质回归总方法ajax,只不过提前知道了一些配置项而已)
["get", "head", "delete", "options"].forEach(name => {
ajax[name] = function (url, config = {}) {
config = {
url,
method: name,
...config,
};
return ajax(config);
};
});
["post", "put"].forEach(name => {
ajax[name] = function (url, data = {}, config = {}) {
config = {
url,
method: name,
data,
...config,
};
return ajax(config);
};
});
ajax["all"] = function (arr = []) {
return Promise.all(arr);
};
ajax["stringify"] = function (obj) {
// 把一个对象变为 x-www-form-urlencoded 格式
let str = ``;
Object.keys(obj).forEach(key => {
str += `&${key}=${obj[key]}`;
});
return str.substring(1);
};
// 暴露API
typeof window !== "undefined" ? (window.ajax = ajax) : null;
typeof module !== "undefined" && typeof module.exports !== "undefined"
? (module.exports = ajax)
: null;
})();