XHR|Ajax|Axios|Fetch
XMLHttpRequest
XMLHttpRequest XHR 对象用于与服务器交互。通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL 并获取数据。这允许网页在不影响用户操作的情况下,更新页面的局部内容。XMLHttpRequest 在 AJAX 编程中被大量使用。
XMLHttpRequest 可以用于获取任何类型的数据,而不仅仅是 XML。支持 HTTP 以外的协议 file:// 和 FTP,尽管可能受到更多出于安全等原因的限制。
当下的标准浏览器支持 XMLHttpRequest(IE 浏览器使用 ActiveXObject)与服务器交换数据。常规的步骤是先进行新建实例,再初始化请求,最后进行发送,在发送期间可以通过监听就绪状态的函数指定相关回调。
const xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP")
// open 方法接受三个参数: 请求类型、请求 URL、是否异步的布尔值 => 不会实际发送数据而仅做准备、定义请求
xhr.open("get", "http://localhost:3001/xhr", true); // 初始化请求
xhr.setRequestHeader('Content-Type', 'application/json'); //设置 http 头信息
xhr.onreadystatechange = function (res) { // 指定回调函数
if (this.readyState === 4 && xhr.status === 200 && xhr.statusText === 'OK') { // this 为 XMLHttpRequest 实例对象 xhr
let headers = xhr.getAllResponseHeaders();
console.log(headers); // content-length: 31 \ncontent-type: text/html; charset=utf-8
console.log(res); // {... , currentTarget{}, ...}
console.log(res.currentTarget.response); // zsxzy finished the xhr request!
}
}
// send 接受一个参数作为请求体发送的数据 -> 通常在 POST 请求中含参
xhr.send(); // 发送请求 - xhr.send("name=xzy&age=23");
/* xhr 实例属性与方法 */
// timeout => 返回一个整数表示多少毫秒后,如果请求仍然没有得到结果就会自动终止.该属性等于0就表示没有时间限制.
// upload => xhr 不仅可以发送请求还可以发送文件.发送文件后通过 xhr.upload 属性可以得到一个掌握上传进度的对象.
// withCredentials => 布尔值.跨域请求用户信息(Cookie\HTTP 头信息)是否会包含在请求中,默认为 false,即向 example.com 发出跨域请求时,不会发送其设置在本机上的 Cookie.
// abort() => 终止已发出的请求.调用此方法后 readyState 变为4,status 属性变为0.
// 代码在发出5秒之后,终止一个 AJAX 请求
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://blog.zairesinatra.com/xhraborttest', true);
setTimeout(function () { if (xhr) { xhr.abort(); xhr = null; } }, 5000);
// setRequestHeader() => 通过接收接受两个参数 (头信息的字段名,字段值) => 设置浏览器发送请求的头部信息.该方法必须在open()之后、send()之前调用.
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
READYSTATE | 状态 | 描述 |
---|---|---|
0 | UNSENT | 代理被创建,但尚未调用 open() 方法 |
1 | OPENED | open() 方法已被调用 => 可通过 setRequestHeader() 方法设置请求头 or 调用 send() 方法来发起请求 |
2 | HEADERS_RECEIVED | send() 方法已被调用且头部和状态已经可获得 => 响应头已被接收 |
3 | LOADING | 下载中;responseText 属性已经包含部分数据 => 响应体部分正在被接收 |
4 | DONE | 请求操作已经完成 => 数据传输已经彻底完成或失败 |
STATUS | 描述 |
---|---|
200 | OK |
301 | 302 | 304 | 307 | Moved Permanently | Moved temporarily | Not Modified | Temporary Redirect |
401 | 403 | 404 | Unauthorized | Forbidden | Not Found |
500 | Internal Server Error |
Ajax Asynchronous JavaScript and XML
少量数据更新时没有必要让服务端返回全页结构,因页面的重新载入会消耗更多的时间。现今通常会采用局部刷新的方式,让服务端返回必要的数据通过 JavaScript 来控制渲染。更完全一点就是单页面应用,至始至终停滞于特定页面,不会有跳转页面的情况。客户端与服务端的具体异步通信实现依赖于 Ajax。
Ajax 的核心对象是 XMLHttpRequest,通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL 获取数据。
// 原生封装 Ajax - get
function ajaxGet(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
callback(xhr.response);
}
};
}
// 调用
ajaxGet("http://localhost:3000/ajaxget", function (data) {
console.log(data);
});
// 原生封装 Ajax - post
function ajaxPost(url, data, callback) {
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send(data);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
callback(xhr.responseText);
}
}
}
// 调用
ajaxPost('http://localhost:3000/ajaxpost', 'name=zs&age=23', function (data) {
console.log(data);
});
// server => index.js
const Koa = require('koa');
const Router = require('@koa/router');
const cors = require('koa-cors');
var bodyParser = require('koa-bodyparser'); // 获取post请求的参数
const app = new Koa();
const router = new Router();
app.use(bodyParser());
router.get('/', async (ctx, next) => {
console.log("@koa/router get request => ",`${ctx.method} ${ctx.url}`)
ctx.body = 'Hello World';
await next();
});
router.get('/ajaxget', async (ctx, next) => {
const data = "ajaxget request success";
ctx.body = data;
await next();
});
router.post('/ajaxpost', async (ctx, next) => {
const {name,age} = ctx.request.body;
const data = `ajaxpost request success => ${name} - ${age} `;
ctx.body = data;
await next();
});
app.use(cors()).use(router.routes()).use(router.allowedMethods());
app.listen(3000);
Axios
Features =>
Make XMLHttpRequests from the browser; Make http requests from node.js; Supports the Promise API; Intercept request and response; Transform request and response data; Cancel requests; Automatic transforms for JSON data; Automatic data object serialization to multipart/form-data and x-www-form-urlencoded body encodings; Client side support for protecting against XSRF
o快速上手
-
json-server => 快速搭建 Http 服务 => 借助其模拟数据以及延迟返回
-
基于 xhr 和 Promise 的异步请求库 axios
-
axios 请求的响应对象结构
// 配置对象 => 请求类型、url、请求体...
config: {transitional: {…}, transformRequest: Array(1), transformResponse: Array(1), timeout: 0, adapter: ƒ, …}
// 响应体的结果 => axios 会对响应结果进行 json 解析 -> 所以这里拿到的是对象,方便处理
data: {id: 2, title: 'okk', author: 'okkAuthor'}
// 响应头对象
headers: {cache-control: 'no-cache', content-length: '56', content-type: 'application/json; charset=utf-8', expires: '-1', pragma: 'no-cache'}
// 原生的 ajax 请求对象
request: XMLHttpRequest {onreadystatechange: null, readyState: 4, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, …}
// 响应状态码
status: 200
// 响应状态字符串
statusText: "OK"
测试一下测试一下测试一下测试一下测试一下测试一下测试一下测试一下测试一下测试一下测试一下测试一下
baseURL => 基地址或默认地址 -> 在发出请求的时候添加在请求前面的字段 -> 通常指向请求地址
maxContentLength http响应体最大尺寸;maxBodyLength请求体最大内容 => 单位是字节
socketPath 设置 socket 文件位置,如果设置 socket 又设置 proxy,则会优先 socket
decompress: true // default 对响应结果做解压
axios 默认配置 => 重复的配置放入默认配置中简化代码 => 不用新建文件,在组件中的js区域写就行。整个请求对象中的配置都可以设置。
请求拦截器对内容、参数处理或者检验,没有问题再发出,有问题就停止取消;响应拦截器在开发者处理结果前进行预处理,失败了对结果做提醒记录,数据结果进行格式化处理,没有问题再交由开发者自定义的回调处理。
源码知道为啥拦截器顺序是请求拦截器后进先执行,响应拦截器是先进后执行
abortController 判断用户上一次请求是否完成,多次请求就直接给他上一次请求取消掉
- axios 取消请求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>取消请求</title>
<link
crossorigin="anonymous"
href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.min.css"
rel="stylesheet"
/>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
</head>
<body>
<div class="container">
<h2 class="page-header">axios取消请求</h2>
<button class="btn btn-primary">发送请求</button>
<button class="btn btn-warning">取消请求</button>
</div>
<script>
const btns = document.querySelectorAll("button");
let cancel = null;
// 发送请求
btns[0].onclick = function () {
// 检测上一次的请求是否已经完成
if (cancel !== null) {
// 取消上一次的请求
cancel();
}
axios({
method: "GET",
url: "http://localhost:3000/posts",
cancelToken: new axios.CancelToken(function (ok) {
cancel = ok;
}),
}).then((response) => {
console.log(response);
//将 cancel 的值初始化
cancel = null;
});
};
//绑定第二个事件取消请求
btns[1].onclick = function () {
cancel();
};
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>取消请求</title>
<link
crossorigin="anonymous"
href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.min.css"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
<div class="container">
<h2 class="page-header">axios 取消请求</h2>
<button class="btn btn-primary">发送请求</button>
<button class="btn btn-warning">取消请求</button>
</div>
<script>
const btns = document.querySelectorAll("button");
let controller;
let signal;
let flag = null;
// 发送请求
btns[0].onclick = function () {
// 检测上一次的请求是否已经完成
if (typeof flag === "symbol") {
// 取消上一次的请求
controller.abort();
} else {
flag = Symbol();
}
controller = new AbortController();
signal = controller.signal;
axios({
method: "GET",
url: "http://localhost:3000/posts",
signal,
})
.then((response) => {
console.log(response);
})
.catch((err) => {
console.log(err);
// 切记这里不能加下一行代码
// flag = null
});
};
btns[1].onclick = function () {
controller.abort();
};
</script>
</body>
</html>
SCA
- 文件结构
tree ./node_modules/axios
./node_modules/axios
├── dist # 存放打包后的文件
│ ├── axios.js # 未压缩
│ ├── axios.map # Source Map 文件 => 代码出错时定位其在源码中的位置
│ └── axios.min.js # 压缩后
├── index.d.ts # ts 的描述文件 => ts 写完后用 js 发布就需要文件标记对象类型
├── index.js # axios 入口文件 => 引入的 /lib/axios.js
├── lib # 核心目录
│ ├── adapters # 定义请求的适配器 xhr、http
│ │ ├── http.js # nodeJS 端发送 http 请求
│ │ └── xhr.js # 前端发送 ajax 请求
│ ├── axios.js # axios 入口文件 => 请求从此处开始
│ ├── cancel # 定义取消功能
│ │ ├── Cancel.js # 创建取消时的错误对象
│ │ ├── CancelToken.js # 创建取消请求的构造函数
│ │ └── isCancel.js # 检测参数是否为取消对象
│ ├── core
│ │ ├── Axios.js # 存放 axios 构造函数
│ │ ├── InterceptorManager.js # 拦截器管理器构造函数
│ │ ├── buildFullPath.js # 构建完整 URL 的函数
│ │ ├── createError.js # 创建指定信息的错误对象
│ │ ├── dispatchRequest.js # 发送请求的函数 => 去调用对应适配器
│ │ ├── enhanceError.js # 更新错误对象函数
│ │ ├── mergeConfig.js # 合并配置文件
│ │ ├── settle.js # settle 结束 => 根据状态码改变 Promise 状态
│ │ └── transformData.js # 数据格式化转换函数
│ ├── defaults.js # axios 默认配置文件
│ ├── helpers # 存放功能函数
│ │ ├── buildURL.js # 创建 url 并在其后方放入参数
│ │ ├── combineURLs.js # 合并 url => baseURL + relativeURL
│ │ ├── cookies.js # cookie 的处理 => 读取、写入...
│ │ ├── deprecatedMethod.js # 控制台提示不赞成使用的方法
│ │ ├── isAbsoluteURL.js # 判断 url 是否为绝对路径
│ │ ├── isURLSameOrigin.js # 检查是否是同源的 url => 来自于同一个服务
│ │ ├── normalizeHeaderName.js # 统一化头信息、统一变大写
│ │ ├── parseHeaders.js # 对头信息解析的函数
│ │ └── spread.js # 一个以数组调用函数的语法糖拓展
│ └── utils.js # 工具函数
└── package.json
- axios 创建过程
node_modules/axios 中的 index.js 导入同路径下的 lib/axios.js。作为实际的入口文件,axios.js 中又会引入多种相关的模块。
/* axios.js */
var utils = require('./utils'); // 引入工具
var bind = require('./helpers/bind'); // 引入绑定函数 => 创建函数
var Axios = require('./core/Axios'); // 引入 Axios 主文件
var mergeConfig = require('./core/mergeConfig'); // 引入合并配置的函数
var defaults = require('./defaults'); // 导入默认配置
axios.js 中会通过导入的默认配置 defaults 作为参数传入 createInstance 函数的调用生成 axios,并向这个对象上添加属性。
/**
* Create an instance of Axios
* @param {Object} defaultConfig The default config for the instance
* @return {Axios} A new instance of Axios
*/
function createInstance(defaultConfig) {
// 创建一个实例对象 context 可以调用 get、post、put、delete、request
var context = new Axios(defaultConfig); // context 不能当函数使用
// 将 request 方法的 this 指向 context 并返回新函数 instance 可以用作函数使用, 且返回的是一个 promise 对象
var instance = bind(Axios.prototype.request, context); // instance 与 Axios.prototype.request 代码一致
// instance({method:'get'}); instance.get() .post()
// Copy axios.prototype to instance
// 将 Axios.prototype 和实例对象的方法都添加到 instance 函数身上
utils.extend(instance, Axios.prototype, context); // instance.get instance.post ...
// instance() instance.get()
// 将实例对象的方法和属性扩展到 instance 函数身上
utils.extend(instance, context);
return instance;
}
// axios.interceptors
// Create the default instance to be exported
// 通过配置创建 axios 函数
var axios = createInstance(defaults);
// Expose Axios class to allow class inheritance
// axios 添加 Axios 属性, 属性值为构造函数对象 axios.CancelToken = CancelToken new axios.Axios();
axios.Axios = Axios;
// Factory for creating new instances
// 工厂函数 用来返回创建实例对象的函数
axios.create = function create(instanceConfig) {
return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
// Expose Cancel & CancelToken
axios.Cancel = require("./cancel/Cancel");
axios.CancelToken = require("./cancel/CancelToken");
axios.isCancel = require("./cancel/isCancel");
// Expose all/spread
axios.all = function all(promises) {
return Promise.all(promises);
};
axios.spread = require("./helpers/spread");
cancelToken 实例身上维护了属性Promise,并将改变其状态的变量暴露全局
从功能上 axios 拥有 Axios 上的方法,所以可以看作是实例。拓展的方式将 Axios.prototype 的方法加入函数对象身上。
okkkkk
即可以通过向 axios 传递相关配置来创建请求。
// 获取远端图片
axios({
method:'get',
url:'http:/zaire-xie/zairesinatrasource',
responseType:'stream'
})
.then(function(response) {
response.data.pipe(fs.createWriteStream('treasure.jpg'))
// pipe 是将 response.data 转换为流
// fs 是 Node.js 的 file-system 模块
});
Fetch
测试一下测试一下测试一下测试一下测试一下测试一下测试一下测试一下测试一下测试一下测试一下
Fetch API 是用于请求资源的全域方法,相比 XMLHttpRequest 在处理上只能使用回调函数,以及单一却接口
fetch 号称 AJAX 的替代品在 ES6 出现,基于 ES6 的 promise 对象。Fetch 的代码结构较 ajax 更简单。但 fetch 不是 ajax 的进一步封装,而是原生 js,且没有使用 XMLHttpRequest 对象。XMLHttpRequest 可以选择异步,但是 Fetch 必须是异步。
分派请求
fetch 函数只有一个资源 url 作为必须参数。该方法返回一个期约。
let zs = fetch('http://127.0.0.1/zairesinatra')
console.log(zs) // Promise<pending>
fetch("http://127.0.0.1/xzy")
.then((response)=>{console.log(response)})
// response{type:"cors",url:...}
读取响应
读取响应内容最简单方式是取得纯文本格式内容,这里用到 text()
API。这个方法返回一个期约Promise实例对象。
fetch("http://127.0.0.1/zs")
.then((response) => response.text())
.then((msg) => console.log(msg)); // {"msg":"xieziyi"}
处理状态码和请求失败
Fetch API通过 Response 的 status(状态码) 和 statusText(状态文本) 属性检查响应状态。获取响应成功请求通常产生 200 的状态码与 "OK" 的状态文书。
Free and Open Public APIs
结束
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议,转载请注明出处!