异步的JavaScript与XML技术( Asynchronous JavaScript and XML )
Ajax 不需要任何浏览器插件,能在不更新整个页面的前提下维护数据,但需要用户允许JavaScript在浏览器上执行。
兼容性
封装 XMLHttpRequest 对象
1 // 创建 构造函数
2 function Ajax(obj) {
3 this.url = obj.url ||'';
4 this.type = obj.type || 'get';
5 this.data = obj.data ||{};
6 this.success = obj.success || null;
7 this.error = obj.error || null;
8 }
9 // 原型上创建方法支持 post 和 get
10 Ajax.prototype.send = function(){
11 var self = this;
12 var toStr = Object.prototype.toString;
13 if (self.data === null && typeof self.data !== 'object' && Array.isArray(obj)) return;
14 return (function(){
15 // 实例化 XML对象
16 var xhr = new XMLHttpRequest();
17 var data = '';
18 // 序列化参数
19 for (var k in self.data){
20 data += k + '=' + self.data[k] + '&';
21 }
22 data = data.substr(0,data.length - 1);
23 // 接收回调函数
24 xhr.onreadystatechange = function(){
25 if (xhr.readyState === 4){
26 if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
27 isFunction(self.success) && self.success(xhr.responseText)
28 }else{
29 isFunction(self.error) && self.error(xhr)
30 }
31 }
32 }
33 // 初始化请求
34 if(self.type.toLocaleLowerCase() === 'post'){
35 xhr.open ('post',self.url,true)
36 // 设置请求头
37 xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
38 //发送请求
39 xhr.send(data)
40 } else {
41 xhr.open('get', self.url + "?" + data,true)
42 xhr.send(null)
43 }
44 }());
45 };
46
47 function isFunction(obj){
48 return toStr.call(obj) === "[object Function]"
49 }
50
51 var ajax = new Ajax({
52 type:'post',
53 url:"/login",
54 data:{
55 loginname:"admin",
56 password:"admin"
57 },
58 success:function(e){
59 console.log(e)
60 },
61 error:function(err){
62 console.log(err)
63 },
64 }).send();
XMLHttpRequest Level 2 相比于 老版本的 XMLHttpRequest 新增以下内容:
可以设置 HTTP 请求超时时间
1 var xhr = XMLHttpRequest();
2 xhr.open('GET'.'url');
3 // 超时 2s
4 xhr.timeout = 2000;
5 // 超时处理
6 xhr.ontimeout = function(e) {
7 console.log(e)
8 }
9 xhr.send(null)
可以通过 FormData 发送表单数据
1 // 实例化 FormData
2 var formData = new FormData();
3 // 添加数据
4 formData.append(key,value);
5
6 xhr.open('POST','url');
7 xhr.send(formData);
可以上传文件
- FormData 除了可以添加字符串数据,也可以添加 blob、file 类型的数据,因此可以用于上传文件。
- 在浏览器中,一般是通过文件上传输入框来获取 file 对象,比如:
1 <input type="file" name='uploadFile' id="upload-file" />
1 document.getElementById('upload-file')
2 .addEventListener('change', function () {
3
4 var formData = new FormData();
5 // 获取数据
6 formData.append('uploadFile', this.files[0])
7 xhr.send(formData)
8 })
支持跨域请求
- 浏览器默认是不允许跨域请求的,有时候又是必要的,在以前通常使用JSONP来解决(IE10 以下不支持)
- 为了标准化跨域请求, W3C提出 跨域资源共享(CORS)前端无须修改代码,只需 服务器返回 Access-Control-Allow-Origin 响应头,指定允许对应的域
- CORS 默认不发送 cookie 如果需要发送,前端需要设置 withCredentials 属性,同时服务器需要 返回 Access-Control-Allow-Credentials: true,
xhr.withCredentials = true;
可以获取服务端二进制数据
1. 使用 overrideMimeType 方法覆写服务器指定的 MIME 类型,从而改变浏览器解析数据的方式
1 // 参数 MIME 类型
2 // 告诉浏览器,服务器响应的内容是用户自定义的字符集
3 xhr.overrideMimeType('text/plain; charset=x-user-defined');
4 // 浏览器就会将服务器返回的二进制数据当成文本处理,我们需要做进一步的转换才能拿到真实的数据
5 // 获取二进制数据的第 i 位的值
6 var byte = xhr.responseText.charCodeAt(i) & 0xff
"& 0xff" 运算 参考 阮一峰的文章
2.xhr.responseType 用于设置服务器返回的数据的类型,将返回类型设置为 blob 或者 arraybuffer,然后就可以从 xhr.response 属性获取到对应类型的服务器返回数据。
1 xhr.responseType = 'arraybuffer'
2 xhr.onload = function () {
3 var arrayBuffer = xhr.response
4 // 接下来对 arrayBuffer 做进一步处理...
5 }
可以获取数据传输进度信息 参考资料
使用 onload 监听了一个数据传输完成的事件。
1 // 上传进度监听
2 xhr.upload.addEventListener('progress', onProgressHandler, false);
3
4 // 传输成功完成
5 xhr.upload.addEventListener('load', onLoadHandler, false);
6 // 传输失败信息
7 xhr.upload.addEventListener('error', onErrorHandler, false);
更多资料参考: 阮一峰的文章 MDN
AXIOS
- 基于 Promise 的 Http 库
- 可以在客户端 和 nodeJs中使用
- 在客户端创基 XMLHttpRequests
- 在nodeJs 创建 HTTP 请求
- 支持Promise
- 可拦截转化请求和响应数据
- 取消请求
- 自动转化JSON数据
- 支持客户端 XSRF
兼容性
安装
1 npm install axios
methods
Get
1 const axios = require('axios')
2
3 axios.get('url?id=xxx')
4 .then(res => {
5 console.log(res)
6 })
7 .catch(err =>{
8 console.log(err)
9 })
10 //or
11 axios.get('url',{
12 params:{
13 id:'xxxxx'
14 }
15 })
16 .then(res =>{
17 console.log(res)
18 })
19 .catch(err =>{
20 console.log(err)
21 })
同样的传参方法有 delete
post
axios.post('url',{name:'Owen'})
.then(res =>{
console.log(res)
})
.catch(err =>{
console.log(err)
})
同样的传参方法有 put patch
concurrent requests
1 axios.all([axios.get('url1'),axios.get('url2')])
API
axios(config)
1 axios({
2 method:'get', // default is get
3 url:'url', // request url
4 data:{ // 仅支持post,put和patch方法,数据作为请求主体发送 ( Only the post,put and patch methods are supported, and the data is sent as the request body )
5 /* 浏览器仅支持传递 FormData, File, Blob (The browser only supports passing FormData, File and Blob)
6 Node 仅支持传递 Stream, Buffer (The Node only supports passing Stream, Buffer)
7 */
8 name:'owen'
9 },
10 baseURL:'base/url', // 除非url是绝对路径,否则将baseURL添加到url的前面 (Add baseURL to then front of the url unless the url is an absolute path)
11 transformRequest: [function (data, headers) {
12 // 可以修改发送的请求数据和请求头,只支持put,post和patch,回调函数必须返回Buffer,ArrayBuffer,FormData或Stream数据
13 // Can modify the sent request data and request header,only support put, post and patch.
14 // Callback must return Buffer, ArrayBuffer, FormData or Stream data
15
16 // Do whatever you want to transform the data
17
18 return data;
19 }],
20 transformResponse: [function (data) {
21 // 修改响应数据,再传递给 then或catch 方法 (Modify the response data and pass it to the then or catch method)
22 // Do whatever you want to transform the data
23
24 return data;
25 }],
26 headers: {'X-Requested-With': 'XMLHttpRequest'}, // 自定义请求头 (Custom request header)
27 params:{ // 添加到url尾部的参数,一般用于get 和 delete( Parameters addde to the end of the url,generally used for get and delete )
28 id:'xxx'
29 },
30 paramsSerializer: function (params) { //序列化 [params] (https://www.npmjs.com/package/qs)
31 return Qs.stringify(params, {arrayFormat: 'brackets'})
32 },
33 timeout:1000,// default is 0 , 设置请求超时时间,单位毫秒 ( Set request timeout in milliseconds )
34 withCredentials: true, // default is false, 跨域时是否携带cookie( Whether to carry cookies when crossing domains )
35 adapter: function (config) {
36 /*拦截响应数据*/
37 // At this point:
38 // - config has been merged with defaults
39 // - request transformers have already run
40 // - request interceptors have already run
41
42 // Make the request using config provided
43 // Upon response settle the Promise
44 return new Promise(function(resolve, reject) {
45
46 var response = {
47 data: responseData,
48 status: request.status,
49 statusText: request.statusText,
50 headers: responseHeaders,
51 config: config,
52 request: request
53 };
54
55 settle(resolve, reject, response);
56
57 // From here:
58 // - response transformers will run
59 // - response interceptors will run
60
61 /**
62 * Resolve or reject a Promise based on response status.
63 *
64 * @param {Function} resolve A function that resolves the promise.
65 * @param {Function} reject A function that rejects the promise.
66 * @param {object} response The response.
67 */
68 function settle(resolve, reject, response) {
69 var validateStatus = response.config.validateStatus;
70 if (!validateStatus || validateStatus(response.status)) {
71 resolve(response);
72 } else {
73 reject(createError(
74 'Request failed with status code ' + response.status,
75 response.config,
76 null,
77 response.request,
78 response
79 ));
80 }
81 };
82 /**
83 * Create an Error with the specified message, config, error code, request and response.
84 *
85 * @param {string} message The error message.
86 * @param {Object} config The config.
87 * @param {string} [code] The error code (for example, 'ECONNABORTED').
88 * @param {Object} [request] The request.
89 * @param {Object} [response] The response.
90 * @returns {Error} The created error.
91 */
92 function createError(message, config, code, request, response) {
93 var error = new Error(message);
94 return enhanceError(error, config, code, request, response);
95 }
96
97 /**
98 * Update an Error with the specified config, error code, and response.
99 *
100 * @param {Error} error The error to update.
101 * @param {Object} config The config.
102 * @param {string} [code] The error code (for example, 'ECONNABORTED').
103 * @param {Object} [request] The request.
104 * @param {Object} [response] The response.
105 * @returns {Error} The error.
106 */
107 function enhanceError(error, config, code, request, response) {
108 error.config = config;
109 if (code) {
110 error.code = code;
111 }
112
113 error.request = request;
114 error.response = response;
115 error.isAxiosError = true;
116
117 error.toJSON = function() {
118 return {
119 // Standard
120 message: this.message,
121 name: this.name,
122 // Microsoft
123 description: this.description,
124 number: this.number,
125 // Mozilla
126 fileName: this.fileName,
127 lineNumber: this.lineNumber,
128 columnNumber: this.columnNumber,
129 stack: this.stack,
130 // Axios
131 config: this.config,
132 code: this.code
133 };
134 };
135 return error;
136 }
137 });
138 },
139 auth:{ // 表示应使用HTTP Basic身份验证,并提供凭据 ( indicates that HTTP Basic auth should be used, and supplies credentials. )
140 user:'xxx',
141 password:'***'
142 },
143 responseType: 'json',/* 服务器响应的数据类型( The server response data type )
144 支持 arraybuffer, blob, document, json, text, stream
145 */
146 responseEncoding:'utf8', // 用于解码响应的编码 (Encoding for decoding the response )
147 xsrfCookieName: 'XSRF-TOKEN', // default is XSRF-TOKEN , csrf令牌Cookie 名称
148 xsrfHeaderName: 'X-XSRF-TOKEN', //default is X-XSRF-TOKEN, xsrf标记值的http标头的名称
149 onUploadProgress: function (progressEvent) { //上传进度事件 (handling of progress events for uploads )
150 console.log(progressEvent)
151 },
152 onDownloadProgress: function (progressEvent) { // 下载进度事件 ( handling of progress events for downloads)
153 console.log(progressEvent)
154 },
155 maxContentLength: 2000, // 允许响应内容的最大字节 (defines the max size of the http response content in bytes allowed)
156 validateStatus: function (status) { // 返回给定HTTP状态范围, 如果状态在给定范围内,响应数据传给`then` ,否则传给 `catch` ( Returns the given HTTP status range, if the status is within the give range, the respones data is passed to `then`, otherwise passed to `catch` )
157 return status >= 200 && status < 300; // default
158 },
159 maxRedirects: 5, // default is 5 // 定义Node 中最大重定向数 ( defines the maximunn number of redirects in Node )
160 socketPath: null, // default is null 定义要在node.js中使用的 UNIX socket
161 httpAgent: new http.Agent({ keepAlive: true }), // node 中 http 和 https 的代理
162 httpsAgent: new https.Agent({ keepAlive: true }),// http://nodejs.cn/api/http.html
163 proxy: { // 代理配置
164 host: '127.0.0.1',
165 port: 9000,
166 auth: {
167 username: 'mikeymike',
168 password: 'rapunz3l'
169 }
170 },
171 cancelToken: new CancelToken(function (cancel) { // 取消请求的 token
172 })
173 })
174 .then(res =>{
175 console.log(res)
176 })
177 .catch(err =>{
178 console.log(err)
179 })
全局配置
通过 axios.create 方法来替换全局配置
const instance = axios.create({
baseURL: 'base/url'
});
通过axios.defaults 对象替换全局默认配置
1 instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;
2 instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
拦截器
拦截请求数据
1 axios.interceptors.request.use(function (config) {
2 return config;
3 }, function (error) {
4 return Promise.reject(error);
5 });
拦截响应数据
1 axios.interceptors.response.use(function (response) {
2 // Do something with response data
3 return response;
4 }, function (error) {
5 // Do something with response error
6 return Promise.reject(error);
7 });
删除拦截器
1 const myInterceptor = axios.interceptors.request.use(function () {/*...*/});
2 axios.interceptors.request.eject(myInterceptor);
查阅更多信息
Axios 二次封装
- 核心文件
1 /**
2 * @desc: axios封装
3 * @author: ggw
4 * @module: axios
5 * @description: 配合使用 饿了么的 Message和Loading
6 *
7 */
8 import axios from 'axios';
9 import qs from 'qs';
10 import {
11 Message,
12 Loading
13 } from 'element-ui';
14
15 import router from '../router';
16 let loading;
17 let headerNone = {
18 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
19 };
20 let headerTwo = {
21 'Content-Type': 'application/json; charset=UTF-8'
22 };
23 let baseURL = window.location.origin ;
24
25
26 /**
27 * @description: 定义初始化Loading
28 * @method: startLoading
29 */
30 const startLoading = () => {
31 loading = Loading.service({
32 target: '.content-box',
33 background: 'rgba(220, 220, 220, 0.51)'
34 });
35 };
36
37
38 let count = 0;
39 /**
40 * @description: 显示Loading 同时多个发送请求 只开启一次Loading
41 * @method: showLoading && hideLoading
42 */
43 const showLoading = () => {
44 if (count === 0) startLoading();
45 count++;
46 };
47 const hideLoading = () => {
48 if (count <= 0) return;
49 count--;
50 if (count === 0) {
51 setTimeout(() => {
52 loading.close();
53 }, 300);
54 }
55 };
56
57 export let filiter = r => {
58
59 for (let item of Object.keys(r)) {
60 if (r[item] === ' ' || r[item] === '') {
61 delete r[item];
62 }
63 }
64 };
65 /**
66 * @description: 出口
67 * @exports api
68 * @param:options 必须是对象
69 * options 对象为 axios对应参数
70 */
71 export default (options) => {
72 /**
73 * @description: 用来初始化承诺的回调。
74 * 这个回调被传递了两个参数:
75 * 一个解析回调用一个值或另一个承诺的结果来解析承诺,
76 * 以及一个拒绝回调,用来拒绝承诺的原因或错误。
77 * @constructor: Promise
78 */
79 return new Promise((resolve, reject) => {
80 const instance = axios.create({
81 withCredentials: true,
82 headers: headerNone,
83 baseURL
84 });
85 // 请求拦截器
86 instance.interceptors.request.use(config => {
87 let {load = true} = config.data || config.params || {} ;
88 if (load) showLoading();
89 // 过滤无值参数
90 if (config.params) {
91 delete config.params.load;
92 filiter(config.params);
93 } else if (config.data) {
94 filiter(config.data);
95 delete config.data.load;
96 }
97 if (
98 config.method.toLocaleLowerCase() === 'post' ||
99 config.method.toLocaleLowerCase() === 'put'
100 ) {
101 // json 格式传递
102 if (config.json) {
103 config.headers = headerTwo;
104 } else {
105 config.data = qs.stringify(config.data);
106 config.data = config.data + '&t=' + Date.now();
107 }
108 }
109 return config;
110 }, error => {
111 hideLoading();
112 return Promise.reject(error);
113 });
114 // 响应拦截器
115 instance.interceptors.response.use(response => {
116 setTimeout(hideLoading,0);
117 let data;
118 // IE9时response.data是undefined,因此需要使用response.request.responseText(Stringify后的字符串)
119 if (!response.data ) {
120 data = response.request.responseText;
121 } else {
122 data = response.data;
123 }
124
125 switch (data.code) { // 接口定义字段
126 case '001':
127 Message({
128 showClose: true,
129 message: data.msg || '未知错误,请联系管理员',
130 type: 'error'
131 });
132 router.push({
133 path: '/login'
134 });
135 break;
136 default:
137 }
138 return data;
139 }, err => {
140 hideLoading();
141
142 if (err && err.response) {
143 let msg = {
144 400: '请求错误',
145 401: '未授权,请登录',
146 403: '拒绝访问',
147 404: `请求地址出错: ${err.response.request.responseURL}`,
148 408: '请求超时',
149 500: '服务器内部错误',
150 501: '服务未实现',
151 502: '网关错误',
152 503: '服务不可用',
153 504: '网关超时',
154 505: 'HTTP版本不受支持'
155 };
156 let status = parseInt(err.response.status,10);
157 Message({
158 showClose: true,
159 message: msg[status] || '',
160 type: 'error'
161 });
162 } else {
163 Message({
164 message: err.config ? `请求地址出错: ${err.config.url}` : err,
165 type: 'error'
166 });
167 }
168
169 return Promise.reject(err);
170 });
171 // 请求
172 instance(options)
173 .then(res => {
174 resolve(res);
175 return false;
176 })
177 .catch(error => {
178 reject(error);
179 });
180 });
181 };
- 导出
import axios from './api';
const get = (url, data) => {
return axios({
url,
method: 'get',
params:data
});
};
const post = (url, data,json) => {
return axios({
url,
method: 'post',
data,
json
});
};
const del = (url, data) => {
return axios({
url,
method: 'delete',
params:data
});
};
const put = (url, data,json) => {
return axios({
url,
method: 'put',
data,
json
});
};
export default {
get,
post,
del,
put
};
- 导入vue main.js
import Vue from 'vue';
import api from './api';
Vue.prototype.$api = api;
- 使用
this.$api.get(url,data,isJson);