1. /*
    2. * 基于Promise封装一个ajax库
    3. * + 参考axios的语法
    4. * + XMLHttpRequest
    5. *
    6. * 基于语法:
    7. * ajax([config])
    8. * ajax.get/head/options/delete([url],[config])
    9. * ajax.post/put([url],[data],[config])
    10. *
    11. * 二次配置:
    12. * + 默认配置项
    13. * + ajax.defaults.xxx 修改默认配置项
    14. * + ajax([config]) 自己传递的配置项
    15. *
    16. * {
    17. * baseURL:'',
    18. * url:'',
    19. * method:'get',
    20. * transformRequest:function(data){return data;}, 请求主体传递信息的格式化
    21. * headers:{
    22. * 'Content-Type':'application/json'
    23. * },
    24. * params:{}, URL传参数信息(拼接到URL的末尾)
    25. * cache:true, GET系列请求中是否清除缓存(?_=随机数)
    26. * data:{}, 请求主体传参信息
    27. * timeout:0, 设置请求超时时间
    28. * withCredentials:false, 跨域请求中允许携带资源凭证
    29. * responseType:'json', 预设服务器返回结果的处理方案 'stream', 'document', 'json', 'text'
    30. * validateStatus: function (status) {
    31. * return status >= 200 && status < 300; // default
    32. * }
    33. * }
    34. *
    35. * 拦截器:
    36. * + 请求拦截器 ajax.interceptors.request(function(config){})
    37. * + 响应拦截器 ajax.interceptors.response(function(response){},function(reason){})
    38. * + ajax.all([promise array])
    39. *
    40. * 基于ajax请求回来的结果都是promise实例
    41. * + response
    42. * + data 响应主体信息
    43. * + status 状态码
    44. * + statusText 状态码的描述
    45. * + headers 响应头信息
    46. * + request XHR原生对象
    47. * + reason
    48. * + response
    49. * + message
    50. * + ...
    51. */
    52. (function () {
    53. // 核心库的封装
    54. function ajax(config = {}) {
    55. return new init(config);
    56. }
    57. ajax.prototype = {
    58. constructor: ajax,
    59. version: "0.0.1",
    60. // 发送ajax请求
    61. send() {
    62. let {
    63. method,
    64. timeout,
    65. withCredentials,
    66. headers,
    67. validateStatus,
    68. adapter2,
    69. } = this.config;
    70. return new Promise((resolve, reject) => {
    71. let xhr = new XMLHttpRequest();
    72. xhr.open(method, this.initURL());
    73. // 基础配置
    74. xhr.timeout = timeout;
    75. xhr.withCredentials = withCredentials;
    76. Object.keys(headers).forEach(key => {
    77. xhr.setRequestHeader(key, headers[key]);
    78. });
    79. xhr.onreadystatechange = () => {
    80. let status = xhr.status,
    81. state = xhr.readyState;
    82. if (!validateStatus(status)) {
    83. // 状态码代表失败(有请求结果)
    84. reject(this.initResult(false, xhr));
    85. return;
    86. }
    87. // 成功
    88. if (state === 4) {
    89. resolve(this.initResult(true, xhr));
    90. }
    91. };
    92. xhr.onerror = err => {
    93. // 请求都是失败的
    94. reject({
    95. message: err.message,
    96. });
    97. };
    98. xhr.send(this.initData());
    99. }).then(
    100. // 响应拦截器:在返回的promise实例和自己调用then中拦截一道
    101. ...adapter2
    102. );
    103. },
    104. // 初始化URL
    105. initURL() {
    106. let { url, baseURL, params, cache } = this.config;
    107. url = baseURL + url;
    108. // 问号参数
    109. params = ajax.stringify(params);
    110. if (params) {
    111. url += `${url.includes("?") ? "&" : "?"}${params}`;
    112. }
    113. // 缓存处理
    114. if (!cache) {
    115. url += `${url.includes("?") ? "&" : "?"}_=${+new Date()}`;
    116. }
    117. return url;
    118. },
    119. // 请求主体信息处理 (只对POST有效)
    120. initData() {
    121. let { method, data, transformRequest } = this.config;
    122. if (this.GET_REG.test(method)) return null;
    123. return transformRequest(data);
    124. },
    125. // 构建成功失败的返回结果
    126. getHeaders(xhr) {
    127. let headersText = xhr.getAllResponseHeaders(),
    128. obj = {};
    129. headersText = headersText.split(/(?:\n)/g);
    130. headersText.forEach(item => {
    131. if (!item) return;
    132. let [key, value] = item.split(": ");
    133. obj[key] = value;
    134. });
    135. return obj;
    136. },
    137. initResult(flag, xhr) {
    138. let { responseType } = this.config;
    139. let response = {
    140. data: {},
    141. request: xhr,
    142. status: xhr.status,
    143. statusText: xhr.statusText,
    144. headers: this.getHeaders(xhr),
    145. };
    146. if (flag) {
    147. // 成功
    148. let res = xhr.responseText;
    149. switch (responseType.toLowerCase()) {
    150. case "json":
    151. res = JSON.parse(res);
    152. break;
    153. case "document":
    154. res = xhr.responseXML;
    155. break;
    156. case "stream":
    157. res = xhr.response;
    158. break;
    159. }
    160. response.data = res;
    161. return response;
    162. }
    163. // 失败
    164. return {
    165. response,
    166. message: xhr.statusText,
    167. };
    168. },
    169. };
    170. function init(config) {
    171. // 自己传递的配置项替换默认配置项
    172. Object.keys(config).forEach(key => {
    173. ajax.defaults[key] = config[key];
    174. });
    175. // 请求拦截器:处理config
    176. defaults = defaults.adapter1[0](defaults);
    177. // 把配置项挂在到实例上
    178. this.config = defaults;
    179. this.GET_REG = /^(GET|HEAD|DELETE|OPTIONS)$/i;
    180. // 发送请求,但是最后返回的是一个promise实例
    181. return this.send();
    182. }
    183. init.prototype = ajax.prototype;
    184. // 默认配置项(基于Proxy劫持数据,控制只允许ajax.defaults.xxx=xxx来修改配置项)
    185. let defaultsType = {
    186. baseURL: "string",
    187. url: "string",
    188. method: "string",
    189. params: "object",
    190. cache: "boolean",
    191. data: "object",
    192. timeout: "number",
    193. withCredentials: "boolean",
    194. responseType: "string",
    195. headers: "object",
    196. transformRequest: "function",
    197. validateStatus: "function",
    198. adapter1: "array",
    199. adapter2: "array",
    200. };
    201. let defaults = {
    202. baseURL: "",
    203. url: "",
    204. method: "get",
    205. params: {},
    206. cache: true,
    207. data: {},
    208. timeout: 0,
    209. withCredentials: false,
    210. responseType: "json",
    211. headers: {
    212. "Content-Type": "application/json",
    213. },
    214. transformRequest: data => JSON.stringify(data),
    215. validateStatus: status => status >= 200 && status < 300,
    216. // 拦截器参数 请求/响应
    217. adapter1: [
    218. function (config) {
    219. return config;
    220. },
    221. ],
    222. adapter2: [
    223. function (response) {
    224. return Promise.resolve(response);
    225. },
    226. function (reason) {
    227. return Promise.reject(reason);
    228. },
    229. ],
    230. };
    231. ajax.defaults = new Proxy(defaults, {
    232. set(obj, prop, value) {
    233. // 拦截器不走这个配置
    234. if (prop === "adapter1" || prop === "adapter2") {
    235. return;
    236. }
    237. // 格式是否合法的处理
    238. let defT = defaultsType[prop] || "string",
    239. valT = value === null ? "null" : typeof value;
    240. if (defT !== valT) {
    241. throw new TypeError(`传参格式错误:${prop}参数的格式必须是${defT}!`);
    242. }
    243. // 和之前的HEADERS信息合并后再处理
    244. if (prop === "headers") {
    245. value = Object.assign(defaults.headers, value);
    246. }
    247. obj[prop] = value;
    248. },
    249. });
    250. // 拦截器的处理
    251. ajax.interceptors = {
    252. request(callback) {
    253. if (typeof callback !== "function") {
    254. throw new TypeError(`传参格式错误:实参的格式必须是函数!`);
    255. }
    256. defaults.adapter1 = [callback];
    257. },
    258. response(success, error) {
    259. // null和undefined不管,其余的处理
    260. if (success != undefined) {
    261. if (typeof success !== "function") {
    262. throw new TypeError(`传参格式错误:实参的格式必须是函数!`);
    263. }
    264. defaults.adapter2[0] = success;
    265. }
    266. if (error != undefined) {
    267. if (typeof error !== "function") {
    268. throw new TypeError(`传参格式错误:实参的格式必须是函数!`);
    269. }
    270. defaults.adapter2[1] = error;
    271. }
    272. },
    273. };
    274. // 快捷调用的办法(最后本质回归总方法ajax,只不过提前知道了一些配置项而已)
    275. ["get", "head", "delete", "options"].forEach(name => {
    276. ajax[name] = function (url, config = {}) {
    277. config = {
    278. url,
    279. method: name,
    280. ...config,
    281. };
    282. return ajax(config);
    283. };
    284. });
    285. ["post", "put"].forEach(name => {
    286. ajax[name] = function (url, data = {}, config = {}) {
    287. config = {
    288. url,
    289. method: name,
    290. data,
    291. ...config,
    292. };
    293. return ajax(config);
    294. };
    295. });
    296. ajax["all"] = function (arr = []) {
    297. return Promise.all(arr);
    298. };
    299. ajax["stringify"] = function (obj) {
    300. // 把一个对象变为 x-www-form-urlencoded 格式
    301. let str = ``;
    302. Object.keys(obj).forEach(key => {
    303. str += `&${key}=${obj[key]}`;
    304. });
    305. return str.substring(1);
    306. };
    307. // 暴露API
    308. typeof window !== "undefined" ? (window.ajax = ajax) : null;
    309. typeof module !== "undefined" && typeof module.exports !== "undefined"
    310. ? (module.exports = ajax)
    311. : null;
    312. })();