@jsongao98
2021-04-24T12:13:06.000000Z
字数 9087
阅读 54
JavaScript
fetch
let response = await fetch(url , [options]);//解析响应头
let result = await response.json(); // 将 body 读取为 json
或者用promise链的方式
fetch(url, options)
.then(response => response.json())
.then(result => /* process result */)第一阶段,当服务器发送了响应头(response header),fetch 返回的 promise 就使用内建的 Response class 对象来对响应头进行解析。如果 fetch 无法建立一个 HTTP 请求,例如网络问题,亦或是请求的网址不存在,那么 promise 就会 reject。异常的 HTTP 状态,例如 404 或 500,不会导致出现 error。 返回的response的属性:
response.status ——— http状态码
response.ok —— 布尔值,如果 HTTP 状态码为 200-299,则为 true。
response.headers —— 类似于 Map 的带有 HTTP header 的对象。 Response header 位于 response.headers 中的一个类似于 Map 的 header 对象。
它不是真正的 Map,但是它具有类似的方法,我们可以按名称(name)获取各个 header,或迭代它们:
let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits');
// 获取一个 header
alert(response.headers.get('Content-Type')); // application/json; charset=utf-8
// 迭代所有 header
for (let [key, value] of response.headers) {
alert(`${key} = ${value}`);
}
第二阶段,获取 response body respronse基于promise的方法,以不同格式访问body,而且只能使用一种:
response.text() —— 读取 response,并以文本形式返回 response,
response.json() —— 将 response 解析为 JSON,
response.formData() —— 以 FormData 对象(在 下一章 有解释)的形式返回 response,
response.blob() —— 以 Blob(具有类型的二进制数据)形式返回 response,
response.arrayBuffer() —— 以 ArrayBuffer(低级别的二进制数据)形式返回 response,
Request header/method/body
- headers :
- Accept
- Accept-Language
- Accept-Charset
- Accept-Encoding
- API-Key
- Content-Language
- Content-Type
- Connection
- Host
- Origin (protocol/domain/port)
- Referer (包含当前页面url:scheme://host.domain:port/path/filename)
- 我们需要 Origin,是因为有时会没有 Referer。例如,当我们从 HTTPS(从高安全性访问低安全性)fetch HTTP 页面时,便没有 Referer。 内容安全策略 可能会禁止发送 Referer。 正如我们将看到的,fetch 也具有阻止发送 Referer 的选项,甚至允许修改它(在同一网站内)。 根据规范,Referer 是一个可选的 HTTP-header。 正是因为 Referer 不可靠,才发明了 Origin。浏览器保证跨源请求的正确 Origin。
- credentials: "include"
- 对应的response header:Access-Control-Allow-Credentials :true
- method : —— HTTP 方法,例如 POST,
- body : —— request body,其中之一:
- 字符串(例如 JSON 编码的),
- FormData 对象,以 form/multipart 形式发送数据,带有 Content-Type: multipart/form-data
- new Fromdata(form) : .append()/.get()/.set()/.has()/.delete()
- 发送带文件的表单:
input type='file' name='picture' accept='image/*' 是被编码允许的- 发送带blob的表单:
let imageBlob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'));
let formData = new FormData();
formData.append("image", imageBlob, "image.png");
就像表单中有 input type="file" name="image"一样,用户从他们的文件系统中使用数据 imageBlob(第二个参数,blob类型)提交了一个名为 image.png(第三个参数)的文件。- Blob/BufferSource 发送二进制数据,
下载进度
fetch 方法无法跟踪 上传 进度,允许去跟踪 下载 进度。
// 代替 response.json() 以及其他方法
const reader = response.body.getReader();
// 在 body 下载时,一直为无限循环,循环下载response chunk
while(true) {
// 当最后一块下载完成时,done 值为 true
// value 是块字节的 Uint8Array
const {done, value} = await reader.read(); //await reader.read() 调用的结果是一个具有两个属性的对象:
//done —— 当读取完成时为 true,否则为 false。
//value —— 字节的类型化数组:Uint8Array。
if (done) {
break;
}
console.log(`Received ${value.length} bytes`)
}
中止(Abort)
一个特殊的内建对象:AbortController。它不仅可以中止 fetch,还可以中止其他异步任务。
abort()方法
signal属性:当abort()被调用时,signal.aborted = true
与fetch一起使用
let controller = new AbortController();
let response = await fetch('/article/fetch-abort/demo/hang', {
signal: controller.signal//fetch同样监听controller的signal属性的变化
});
let urls = [...]; // 要并行 fetch 的 url 列表
let controller = new AbortController();
// 一个 fetch promise 的数组
let fetchJobs = urls.map(url => fetch(url, {
signal: controller.signal
}));
let results = await Promise.all(fetchJobs);
// 如果 controller.abort() 被从其他地方调用,
// 它将中止所有 fetch
与其他异步任务一起使用
let urls = [...];
let controller = new AbortController();
let ourJob = new Promise((resolve, reject) => { // 我们的任务 ...
controller.signal.addEventListener('abort', reject);//************************
});
let fetchJobs = urls.map(url => fetch(url, { // fetches
signal: controller.signal
}));
// 等待完成我们的任务和所有 fetch
let results = await Promise.all([...fetchJobs, ourJob]);
// 如果 controller.abort() 被从其他地方调用,
// 它将中止所有 fetch 和 ourJob
跨域请求的解决方案:因为浏览器同源策略限制
一个源(域domain/端口port/协议protocal三者)无法获取另一个源(origin)的内容。因此,即使我们有一个子域,或者仅仅是另一个端口,这都是不同的源,彼此无法相互访问。
① jsonp
② 跨源请求(CORS)
如果一个请求是跨源的,浏览器始终会向其添加 Origin header。
Resposne header: 对于跨源请求,默认情况下,JavaScript 只能访问“简单” 的response header:
访问任何其他 response header 都将导致 error。
- Cache-Control
- Content-Language
- Content-Type
- Expires
- Last-Modified
- Pragma
要授予 JavaScript 对任何其他 response header 的访问权限,服务器必须发送Access-Control-Expose-Headers header。它包含一个以逗号分隔的应该被设置为可访问的非简单 header 名称列表。比如:
Access-Control-Expose-Headers: Content-Length , API-Key , ...
- 简单请求:
- 方法:GET / POST / HEAD
- 简单请求的header仅能设置:(这三种请求如果自定义请求头的话也会发送option预检请求)
- Accept
- Accept-Language
- Content-Language
- Content-Type 的值为 application/x-www-form-urlencoded,multipart/form-data 或 text/plain。
- 简单请求和其他请求的本质区别在于,自古以来使用 form 或 script 标签进行简单请求就是可行的(标签需要具有 crossorigin 特性),而长期以来浏览器都不能进行非简单请求。
所以,实际区别在于,简单请求会使用 Origin header 并立即发送,而对于其他请求,浏览器会发出初步的“预检”请求,以请求许可。
![]()
- 其他任意请求
https://juejin.cn/post/6844903821634699277
任何“非标准”请求 —— 浏览器不会立即发出在过去无法完成的这类请求。即在它发送这类请求前,会先发送“预检(preflight)”请求来请求许可。
- Step1 预检请求(preflight request)
OPTIONS /service.json
Host: site.com
Origin: https://javascript.info
Access-Control-Request-Method: PATCH
Access-Control-Request-Headers: Content-Type,API-Key
- Step2 预检响应(preflight response)
200 OK
Access-Control-Allow-Origin: https://javascript.info
Access-Control-Allow-Methods: PUT,PATCH,DELETE
Access-Control-Allow-Headers: API-Key,Content-Type,If-Modified-Since,Cache-Control
Access-Control-Max-Age: 86400
- Step3 实际请求(actual request)
PATCH /service.json
Host: site.com
Content-Type: application/json
API-Key: secret
Origin: https://javascript.info
- Step4 实际响应(actual response)
Access-Control-Allow-Origin: https://javascript.info
③ 代理服务器(不需要前后端协同)
URL对象
- href 是完整的 URL,与 url.toString() 相同
- protocol 以冒号字符 : 结尾
- search —— 以问号 ? 开头的一串参数
- hash 以哈希字符 # 开头
- 如果存在 HTTP 身份验证,则这里可能还会有 user 和 password 属性:http://login:password@site.com(图片上没有,很少被用到)。
XHR(XMLHttpRequest)
创建:
let xhr = new XMLHttpRequest();
配置请求:
xhr.open(method,URL,[async,user,password])
同步请求: open 方法中将第三个参数 async 设置为 false,那么请求就会以同步的方式进行。
换句话说,JavaScript 执行在 send() 处暂停,并在收到响应后恢复执行。这有点儿像 alert 或 prompt 命令。
发送请求:
xhr.send([body])
监听xhr对象事件以获得响应:
- 按生命周期:
- loadstart —— 请求开始。
- progress —— 仅下载阶段触发,一个响应数据包到达,此时整个 response body 都在 response 中。
- abort —— 调用 xhr.abort() 取消了请求, 触发abort事件,且 xhr.status 变为0 。
- error —— 发生连接错误,例如,域错误。不会发生诸如 404 这类的 HTTP 错误。
- load —— 请求成功完成。
- timeout —— 由于请求超时而取消了该请求(仅发生在设置了 timeout 的情况下)。
- loadend —— 在 load,error,timeout 或 abort 之后触发。error,abort,timeout 和 load 事件是互斥的。其中只有一种可能发生。
- 服务器有了响应后,从xhr对象属性中获取响应状态:
- status —— HTTP 状态码(一个数字):200,404,403 等,如果出现非 HTTP 错误,则为 0。
- statusText —— HTTP 状态消息(一个字符串):状态码为 200 对应于 OK,404 对应于 Not Found,403 对应于 Forbidden。
- response —— 服务器 response body。
- timeout —— 如果在给定时间内请求没有成功执行,请求就会被取消,并且触发 timeout 事件。
- 用 xhr.responseType来设置响应类型:
- ""(默认)—— 响应格式为字符串,
- "text" —— 响应格式为字符串,
- "arraybuffer" —— 响应格式为 ArrayBuffer(对于二进制数据,请参见 ArrayBuffer,二进制数组),
- "blob" —— 响应格式为 Blob(对于二进制数据,请参见 Blob),
- "document" —— 响应格式为 XML document(可以使用 XPath 和其他 XML 方法),
- "json" —— 响应格式为 JSON(自动解析)。
- xhr.readyState
UNSENT = 0; // 初始状态
OPENED = 1; // open 被调用
HEADERS_RECEIVED = 2; // 接收到 response header
LOADING = 3; // 响应正在被加载(接收到一个数据包)
DONE = 4; // 请求完成
XMLHttpRequest 对象以 0 → 1 → 2 → 3 → … → 3 → 4的顺序在它们之间转变。每当通过网络接收到一个数据包,就会重复一次状态 3。
HTTP-header XMLHttpRequest 允许发送自定义 header,并且可以从响应中读取 header。
- setRequestHeader(name,value)
能设置的请求头有限制且一旦设置不能被移除。
xhr.setRequestHeader('Content-Type', 'application/json');
- getResponseHeader(name)
获取具有给定 name 的 header(Set-Cookie 和 Set-Cookie2 除外)
- getAllResponseHeaders()
返回除 Set-Cookie 和 Set-Cookie2 外的所有 response header。
上传进度:xhr.upload对象 它没有方法,它专门用于跟踪上传事件。它会生成事件,类似于 xhr,但是 xhr.upload 仅在上传时触发它们:
- loadstart —— 上传开始。
- progress —— 上传期间定期触发。
- abort —— 上传中止。
- error —— 非 HTTP 错误。
- load —— 上传成功完成。
- timeout —— 上传超时(如果设置了 timeout 属性)。
- loadend —— 上传完成,无论成功还是 error。
跨源请求 XMLHttpRequest 可以使用和 fetch 相同的 CORS 策略进行跨源请求。
就像 fetch 一样,默认情况下不会将 cookie 和 HTTP 授权发送到其他域。要启用它们,可以将 xhr.withCredentials 设置为 true
短轮询,长轮询,长连接, WebSocket
- 短轮询
- 基本思路就是浏览器每隔一段时间向浏览器发送http请求,服务器端在收到请求后,不论是否有数据更新,都直接进行响应。这种方式实现的即时通信,本质上还是浏览器发送请求,服务器接受请求的一个过程,通过让客户端不断的进行请求,使得客户端能够模拟实时地收到服务器端的数据的变化。
- 不足:
- 消息传递的延迟最多为 10 秒(两个请求之间)
- 即使没有消息,服务器也会每隔 10 秒被请求轰炸一次,即使用户切换到其他地方或者处于休眠状态,也是如此。就性能而言,这是一个很大的负担。
- 因此短轮询不适用于那些同时在线用户数量比较大,并且很注重性能的Web应用。
- 长轮询
- 当服务器收到客户端发来的请求后,服务器端不会直接进行响应,而是先将这个请求挂起,然后判断服务器端数据是否有更新。如果有更新,则进行响应,如果一直没有数据,则到达一定的时间限制(服务器端设置)才返回。 客户端JavaScript响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。如果连接丢失,可能是因为网络错误,浏览器会立即发送一个新请求。
- 优点:长轮询和短轮询比起来,明显减少了很多不必要的http请求次数,相比之下节约了资源。
- 缺点:长轮询的缺点在于,连接挂起也会导致服务器端资源的浪费。
- 使用场景: 在消息很少的情况下,长轮询很有效。
消息比较频繁,那么下面描绘的请求-接收(requesting-receiving)消息的图表就会变成锯状状(saw-like)。 每个消息都是一个单独的请求,并带有 header,身份验证开销(authentication overhead)等。
因此,在这种情况下,首选另一种方法,例如:Websocket 或 Server Sent Events。
- WebSocket
WebSocket是Html5定义的一个新协议,与传统的http协议不同,该协议可以实现服务器与客户端之间全双工通信。
let socket = new WebSocket("ws://javascript.info");//我们更应该使用 wss:// 加密协议
// wss:// 是基于 TLS 的 WebSocket,类似于 HTTPS 是基于 TLS 的 HTTP,传输安全层在发送方对数据进行了加密,在接收方进行解密。
简单来说,首先需要在客户端和服务器端建立起一个连接,这部分需要http。连接一旦建立,客户端和服务器端就处于平等的地位,可以相互发送数据,不存在请求和响应的区别。
- WebSocket 没有跨源限制。
- 浏览器对 WebSocket 支持很好。
- 可以发送/接收字符串和二进制数据。
WebSocket事件
- open —— 连接已建立
- message —— 接收到数据
- error —— WebSocket 错误
- close —— 连接已关闭
WebSocket 方法
- socket.send(data)
- socket.close([code], [reason])
WebSocket 自身并不包含重新连接(reconnection),身份验证(authentication)和很多其他高级机制。因此,有针对于此的客户端/服务端的库,并且也可以手动实现这些功能。
长连接(Server Sent Events) SSE是HTML5新增的功能,全称为Server-Sent Events。它可以允许服务推送数据到客户端。SSE在本质上就与之前的长轮询、短轮询不同,虽然都是基于http协议的,但是轮询需要客户端先发送请求。而SSE最大的特点就是不需要客户端发送请求,可以实现只要服务器端数据有更新,就可以马上发送到客户端。
SSE的优势很明显,它不需要建立或保持大量的客户端发往服务器端的请求,节约了很多资源,提升应用性能。
杂项
fetch目前遇到的问题:
- fetch只对网络请求报错,对400,500都当做成功的请求,需要封装去处理
- fetch默认不会带cookie,需要添加配置项。
- fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费。
- fetch没有办法原生监测请求的进度,而XHR可以。
ajax请求和普通http请求的相同不同:
首先最早是没有Ajax请求的,只有普通的HTTP请求,这个时候发送一次HTTP请求,Server端就会计算后将数据放在一个HTML网页上返回来,客户端需要刷新网页,也就是每次请求都刷新网页。Ajax请求实现了返回xml或者json数据而不是html,然后支持在html不变情况下动态更新页面内容而无需刷新。 不管传统的HTTP请求还是Ajax请求,都有同步和异步两种选项。
配置为同步之后会阻塞浏览器页面的线程(也可能是进程),返回结果前客户端不再响应用户请求。配置为异步之后不会阻塞浏览器线程,继续进行浏览器渲染和响应用户操作,直到response返回后回调函数处理结果。发现ajax的请求,多了一个“X-Requested-With”属性。