一些基本认识
- 通过浏览器访问Web,一般时候就是通过Http协议请求一个页面
Ajax
不是一种技术,而是一个术语,描述了利用现有技术的方法。技术上包括HTML、CSS、JS、DOM、XML等,以及其核心:window.XMLHttpRequest
这个对象——或者我们也可以说是Api。利用Ajax这个结合了各种技术的方法,区别于form表单,在不刷新页面的前提下,通过Http请求更新网页的数据- 在jQuery时代,
$.ajax
是对这个Api的一个封装,提高了兼容性和易用性 fetch
是新一代的Api,也就是说,它的地位和传统的Ajax是对等的,我们说的是window.fetch
,在客户端是基于的Promise的JavaScript接口。axios
也是基于Promise的,看了源码之后,发现它和$.ajax
一样,其实是对XMLHttpRequest
的封装,但是又不止于此,它还支持Node.js环境的http请求——当然具体的Api不一样,但是好处是两边通用。- RESTful:一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。客户端和服务器之间的交互在请求之间是无状态的。从客户端到服务器的每个请求都必须包含理解请求所必需的信息。
Fetch
从XHR到Fetch
- XMLHttpRequest 是一个非常粗糙的api,不符合关注分离(Separation of Concerns)的原则——说的详细点,就是“输入、输出和用事件来跟踪的状态混杂在一个对象里”,配置和调用方式非常混乱,前端不仅要做各个浏览器的兼容性,还饱受回调地狱的折磨。其基于事件的模型与最近JavaScript流行的Promise以及基于生成器的异步编程模型不太搭(事件模型在处理异步上有点过时了)。
- jQuery的AJAX很好的封装了原生AJAX的代码,在兼容性和易用性方面都做了很大的提高,而且jQuery还把JSONP方法封装在了AJAX里面,可以方便的跨域。
- Fetch基于 Promise,旧浏览器不支持,需要pollyfill ES6-promise。
- 其调用返回一个Promise,这个Promise会在请求响应后被Resolve,并传回Response对象。 当遇到网络错误时,Fetch返回的Promise会被Reject,并传回TypeError。
Fetch的使用
使用Fetch大概是下面的样子:
1
2
3
4
5
6
7
8
9
10/* url (必须), options (可选) */
fetch('/some/url', {
method: 'get'
}).then(function(response) {
}).then(function(returnedValue) {
/* 还可以执行其他的,链式处理,将异步变为类似单线程的写法: 高级用法. */
}).catch(function(err) {
/* 出错了;等价于 then 的第二个参数,但这样更好用更直观 */
});
Fetch的Api
Headers
允许对HTTP request和response headers执行各种操作。 这些操作包括:检索, 设置, 添加和删除。var headers = new Headers()
然后有一系列的Api可以去操作,当然也可以在初始化的时候直接传入对象参数Request
表示一次调用的请求信息。可以使用构造函数Request.Request()
来创建一个新的Request对象。mode
属性用来决定是否允许跨域请求,以及哪些response属性可读。可选的mode属性值为same-origin
,no-cors
(默认)以及cors
。credentials
枚举属性决定了Cookies是否能跨域得到。
Response
呈现了对一次请求的响应数据。可以使用Response.Response()
构造函数来创建一个Response对象,但一般更可能遇到的情况是,其他的API操作返回了一个Response对象:Response实例通常在fetch()的回调中获得。- 无论Request还是Response都可能带着body,Request和Response都为他们的body提供了一些方法,这些方法都读取 Response对象并且将它设置为已读(因为Responses对象被设置为了 stream 的方式,所以它们只能被读取一次),并且都返回一个Promise对象。(区别在于格式不同)
Fetch的一些缺陷
- 使用 Fetch 无法取消一个请求。这是因为 Fetch API 基于 Promise,而 Promise 无法做到这一点。
- Fetch在服务器返回4xx、5xx时是不会抛出错误的——不会走Reject,而是Resolve,所以需要增加判断
- 不支持JSONP,但可以引入fetch-jsonp插件
- 没有interceptor(拦截器)的Api,所以一般需要自己封装一层
- 没有获取状态的方法:isRejected,isResolved,标准 Promise 没有提供获取当前状态 rejected 或者 resolved 的方法。只允许外部传入成功或失败后的回调。
- 不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费
- always 可以通过在 then 和 catch 里重复调用方法实现。finally 也类似。progress 这种进度通知的功能还没有用过,暂不知道如何替代。
- Fetch没有办法原生监测请求的进度,而XHR可以
一些Fetch的库
- isomorphic-fetch:支持NodeJS
- whatwg-fetch:一个封装,和babel-pollyfill一样。如果自身支持fetch,直接返回,用自身的。
- Fetch 也可以做现在流行的同构应用,有人基于 Fetch 的语法,在 Node 端基于 http 库实现了 node-fetch,又有人封装了用于同构应用的 isomorphic-fetch。(同构(isomorphic/universal)就是使前后端运行同一套代码的意思,后端一般是指 NodeJS 环境。)
- 支持:
- 由于 IE8 是 ES3,需要引入 ES5 的 polyfill: ES5-shim, ES5-sham
- 引入 Promise 的 polyfill: ES6-promise
- 引入 Fetch 探测库:fetch-detector
- 引入 Fetch 的 polyfill: fetch-ie8
- 可选:如果你还需要使用JSONP,则引入 fetch-jsonp
- 可选:开启 Babel 的 runtime 模式,使用 async/await 语法糖
写法:
- then catch的写法还是有一点怪,但是我们还有es7的
async/await
比如这样,写异步代码就像写同步代码一样爽,await 后面可以跟 Promise 对象,表示等待 Promise resolve() 才会继续向下执行,如果 Promise 被 reject() 或抛出异常则会被外面的 try…catch 捕获。
1
2
3
4
5
6
7
8try {
let response = await fetch(url);
let data = response.json();
console.log(data);
} catch(e) {
console.log("Oops, error", e);
}
// 注:这段代码如果想运行,外面需要包一个 async function
- then catch的写法还是有一点怪,但是我们还有es7的
Promise,generator/yield,await/async 都是现在和未来 JS 解决异步的标准做法,可以完美搭配使用。这也是使用标准 Promise 一大好处。把第三方 Promise 库的代码全部转成标准 Promise,为以后全面使用 async/await 做准备,是非常明智的
Axios
什么是Axios
- Axios是对原生XHR的封装(在这一点上和
$.ajax
一样)的HTTP库 - 它有以下特点
- 利用XMLHttpRequest对象创建请求
- 可以在Node.js环境创建Http请求
- 支持 Promise API
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换 JSON 数据
- 客户端支持防御 XSRF(让你的每个请求都带一个从cookie中拿到的key, 根据浏览器同源策略,假冒的网站是拿不到你cookie中得key的,这样,后台就可以辨别出这个请求是否是用户在假冒网站上的误导输入,从而采取正确的策略。)
- 提供了一些并发请求的接口(重要,方便了很多的操作,后续操作在他们都完成的时候才执行。)
- jQuery可以用JSONP实现跨域请求,但是Axios的作者明确表态不支持JSONP,Axios需要服务端配合设置
Access-Control-Allow-Origin
。
Axios的例子
GET请求:
1
2
3
4
5
6
7
8
9
10
11axios.get('/user', {
params: {
ID: 12345
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});POST请求
1
2
3
4
5
6
7
8
9
10axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});发起并发请求
1
2
3
4
5
6
7
8
9
10
11
12function getUserAccount() {
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
axios.all([getUserAccount(), getUserPermissions()])
.then(axios.spread(function (acct, perms) {
/* 两个请求现在都执行完成 */
}));传参的方法
1
2
3
4
5
6
7
8
9
10
11axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
});
axios('/user/12345');
/* 别名,还有reuqest、get、delete、head、put、patch */
axios.post(url[, data[, config]])请求超时,重新请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33/* 在main.js设置全局的请求次数,请求的间隙 */
axios.defaults.retry = 4;
axios.defaults.retryDelay = 1000;
axios.interceptors.response.use(undefined, function axiosRetryInterceptor(err) {
var config = err.config;
/* If config does not exist or the retry option is not set, reject */
if(!config || !config.retry) return Promise.reject(err);
/* Set the variable for keeping track of the retry count */
config.__retryCount = config.__retryCount || 0;
/* Check if we've maxed out the total number of retries */
if(config.__retryCount >= config.retry) {
/* Reject with the error */
return Promise.reject(err);
}
/* Increase the retry count */
config.__retryCount += 1;
/* Create new promise to handle exponential backoff */
var backoff = new Promise(function(resolve) {
setTimeout(function() {
resolve();
}, config.retryDelay || 1);
});
/* Return the promise in which recalls axios to retry the request */
return backoff.then(function() {
return axios(config);
});
});