1、XMLHttpRequest对象
let xhr = new XMLHttpRequest()
1、使用XHR
-
首先要调用
open()
方法- 参数:请求类型、请求URL、请求是否异步的布尔值
- 不会实际发送请求,只是为发送请求做好准备。
- 只能访问同源URL,即域名相同、端口相同、协议相同。
-
使用
send()
方法发送- 参数:作为请求体发送的数据,如果不需要请求体,传入null
- 调用后请求就会发送到服务器
-
同步请求,代码会等待服务器响应后再继续执行,XHR对象会增加以下属性
responseText
作为响应体返回的文本responseXML
如果响应内容类型是”text/xml”或”application/xml”,那就是包含响应数据的XML DOM文档status
响应的HTTP状态- 需要首先检查
- 2xx表示成功,此时
responseText
或responseXML
会有内容 - 304表示资源未修改过,是从浏览器缓存中直接拿取的
- 2xx表示成功,此时
- 需要首先检查
statusText
响应的HTTP状态描述
-
异步请求
- XHR对象有一个
readyState
属性,表示当前处在请求响应过程中哪个阶段- 0: 未初始化,尚未调用open()方法
- 1: 已打开,调用open(),尚未调用send()方法
- 2: 已发送,调用send(),尚未收到响应
- 3: 接收中,已经收到部分响应
- 4: 完成,已经收到所有响应,可以使用
- 每次值发生变化,就会触发
readystatechange
事件,一般只关心值为4。
let xhr = new XMLHttpRequest() xhr.onreadystatechange = function() { if(xhr.readyState === 4) { if((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304){ alert(xhr.responseText) }else { alert('error') } } } xhr.open("get", "test.txt", true) xhr.send(null) 复制代码
- 在收到请求前取消异步请求
xhr.abort()
- 调用后,XHR对象会停止触发事件,阻止访问这个对象上任何与响应相关的属性。
- XHR对象有一个
2、HTTP头部
- 每个请求和响应都会携带一些头部字段,默认会发送以下头部字段
Accept
:浏览器可以处理的内容类型Accept-Charset
:浏览器可以显示的字符集Accept-Encoding
: 浏览器可以处理的压缩编码类型Accept-Language
: 浏览器使用的语言Connection
: 浏览器与服务器的连接类型Cookie
: 页面中设置的CookieHost
:发送请求的页面所在的域Referer
:发送请求的页面URIUser-Agent
:浏览器用户代理字符串
- 通过
setRequsetHeader()
方法设置额外的请求头部- 两个参数,头部字段的名称和值
- 需在open()之后,send()之前调用
- 使用
getResponseHeader()
方法从XHR对象中获取响应头部- 参数,获取头部的名称
- 使用
getAllResponseHeader()
返回所有响应头部字段
3、GET请求
- 最常用的查询方法,用于向服务器查询某些信息。
- 必要时需要在GET请求的URL后面添加查询字符串参数。
- 查询字符串的每个名和值都必须使用
encodeURIComponent()
编码
function addURLParam(url, name, value) {
url += (url.indexOf("?") == -1 ? "?" : "&")
url += encodeURIComponent(name) + "=" + encodeURIComponent(value)
return url
}
复制代码
4、POST请求
- 用于向服务器发送应该保存的数据。
- 每个POST请求都应该在请求体中携带提交的数据
- POST请求的请求体可以包含非常多的数据,而且数据可以是任意格式
- 可以使用XHR模拟表单提交
- 把
Content-Type
头部设置为application/x-www-formulencoded
- 创建对应格式的字符串
- 把
- POST请求比GET请求占用资源更多,发送相同数量的数据,GET请求比POST要快两倍
5、XMLHttpRequest Level 2
1、FormData 类型
- 便于表单序列化,便于创建与表单类似格式的数据通过XHR发送
let data = new FormData()
data.append("name", "Nicholas")
复制代码
append()
接收两个参数,键和值,相当于表单字段名称和该字段的值。- 直接给
FormData
构造函数传入一个表单元素,也可以将表单中的数据作为键值对填充进去 - 使用FormData不需要给XHR对象显式设置任何请求头部,能根据FormData实例传入的数据类型自动配置相应头部
2、超时
timeout
属性,用来表示发送请求后等待多少毫秒,如果响应不成功就中断请求。- 超时后会触发
timeout
事件。
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
try {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
} catch (ex) {
// 假设由 ontimeout 处理
}
}
};
xhr.open("get", "timeout.php", true);
xhr.timeout = 1000; // 设置 1 秒超时
xhr.ontimeout = function () {
alert("Request did not return in a second.");
};
xhr.send(null);
复制代码
- 超时后readyState仍然会变成4,也会触发
onreadystatechange
事件处理程序,超时后访问status
属性会发生错误,所以使用try/catch语句。
3、overrideMimeType()方法
- 用来重写XHR响应的MIME类型。
- 在
send()
之前设置。
2、进度事件
loadstart
:在接收到响应的第一个字节时触发progress
:在接收响应期间反复触发error
:在请求出错时触发abort
:在调用abort()
终止连接时触发load
:在成功接收完响应时触发loadend
:在通信完成时,且在error、abort、load之后触发
1、load事件
- 为了简化交互模式,不用检查
readyState
属性
2、progress事件
- 在接收数据期间反复触发,触发时会收到
event
对象,其属性target
XHR对象lengthComputable
布尔值,表示进度信息是否可用position
接收到的字节数totalSize
响应的Content-Type头部定义的总字节数
3、跨源资源共享
- 使用XHR进行Ajax通信主要限制是跨源安全策略,默认只能访问与发起请求的页面在同一个域内的资源,可以防止某些恶意行为,跨站资源共享(CORS)定义了浏览器与服务器如何实现跨源通信
- 基本思路是使用自定义的HTTP头部允许浏览器和服务器相互理解,以确定请求或响应应该成功还是失败
- 简单请求,没有自定义头部,请求体是text/plain类型,在发送时会有一个额外头部
Origin
,包含发送请求的页面的源(协议、域名、端口)以便服务器确定是否为其提供响应。如Origin: http://www.xxx.net
- 如果服务器决定响应,那么就应该发送
Access-Control-Allow-Origin
头部,包含相同的源或*
,如Access-Control-Allow-Origin: http://www.xxx.net
- 如果没有这个头部,或者有源但不匹配,表面不会响应浏览器请求。无论请求还是响应都不会包含cookie信息。
- XMLHttpRequest原生支持CORS,要访问不同域的源,传入绝对URL
- 跨域XHR对象允许访问status和statusText属性,允许同步,但有额外限制
- 不能使用
setRequestHeader()
设置自定义头部 - 不能发送和接收
cookie
getAllResponseHeaders()
方法始终返回空字符串
- 不能使用
1、预检请求
- CORS通过预检请求的服务器验证机制,允许自定义头部,除GET和POST之外的方法,以及不同请求体内容类型。需要发送请求,请求使用OPTION方法包含以下头部
Origin
与简单请求相同Access-Control-Request-Method
请求希望使用的方法Access-Control-Request-Header
(可选)要使用逗号分隔的自定义头部列表
- 服务器可以确定是否允许这种类型请求,服务器会在响应中发送如下头部与浏览器沟通信息
Access-Control-Allow-Origin
与简单请求线条Access-Control-Allow-Method
允许的方法(逗号分隔的列表)Access-Control-Allow-Header
服务器允许的头部(逗号分隔的列表)Access-Control-Max-Age
缓存预检请求的秒数
- 预检请求返回后结果会按照返回的指定时间缓存一段时间。
2、凭据请求
- 跨源请求默认不提供凭据,可以将
withCredentials
属性设置为true表明请求会发送凭据 - 如果服务器允许带凭据请求,则会响应
Access-Control-Allow-Credentials: true
- 如果发送了凭据请求而服务器返回的响应中没有这个头部,则浏览器不会把响应交给JavaScript,即responseText是空字符串,status是0,onerror()被调用。
- 服务器也可以在预检请求的响应中发送这个HTTP头部,表明这个源允许发送凭据请求。
4、替代性跨源技术
- 过去跨源需要依赖DOM特性。
1、图片探测
- 利用
<img>
标签实现跨与通信,任何页面都可以跨与加载图片而不用担心限制。可以动态创建图片,然后通过它们的onload和onerror事件处理程序得知何时收到响应。 - 经常用于图片探测,是与服务器之间简单、跨域、单向的通信。数据通过查询字符串发送,响应可以随意设置,一般是位图图片或值为204的状态码。浏览器通过图片探测拿不到任何数据,但可以通过事件知道什么时候能接收到响应。
let img = new Image()
img.onload = img.onerror = function() {
alert('Done!')
}
img.src = "http://www.example.com/test?name=Nicholas"
复制代码
2、JSONP
- JSONP(JSON with padding),在web服务上流行的一种JSON变体。
- 格式分为两部分:回调和数据
- 回调是在页面接收到响应之后应该调用的函数,通常回调函数的名称是通过请求来动态指定的
- 数据就是作为参数传给回调函数的JSON数据。
http://freegeoip.net/json/?callback=handleResponse
,通常以字符串形式指定回调函数名称。- JSONP调用是通过动态创建
<script>
元素并为src属性指定跨域URL实现的。此时的<script>
与<img>
元素类似,能够不受限制地从其他域加载资源。
function handleResponse(response) {
console.log(response)
}
let script = document.createElement("script")
script.src = "http://fxxx/json/?callback=handleResponse"
document.body.insertBefore(script, document.body.firstChild)
复制代码
- 可以直接访问响应,实现浏览器与服务器的双向通信。缺点:
- 是从不同的域拉取可执行代码,如果域不可信,可能在响应中加入恶意内容。
- 不好确定JSONP请求是否失败,虽然HTML5规定了
<script>
元素的onerror事件处理程序,但还没有被任何浏览器实现。需要使用计时器来决定是否放弃等待响应。
5、Fetch API
- 能够执行XMLHttpRequest对象的所有任务,更容易使用,接口更现代化,能够在Web工作线程等Web工具中使用。必须是异步的。
1、基本用法
- 全局作用域下,包括主页面线程、模块和工作线程。
1、分派请求
-
fetch()
必传参数input,一般是要获取资源的URL,返回一个期约 -
请求完成、资源可用是,期约会解决为一个Response对象。
fetch('bar.txt') .then((response) => { console.log(response) }) // Response {type: "basic", url: ...} 复制代码
2、读取响应
-
读取最简单方式是获取纯文本格式内容,
text()
方法。返回一个期约,会被解决为取得资源的完整内容:fetch('bar.txt') .then((response) => { resonse.text().then((data)=> { console.log(data) }) }) fetch('bar.txt') .then((response) => response.text()) .then((data) => console.log(data)) 复制代码
3、处理状态码和请求失败
- 支持通过
Response
的status(状态码)和statusText(状态文本)属性检查响应状态。- 成功获取响应的请求200、请求不存在的资源404、请求的URL如果服务器抛出错误500
- 可以显式设置
fetch()
在遇到重定向时的行为,默认行为是跟随重定向并返回状态码不是300~399的响应,响应对象的redirected
属性会被设置为true,状态码仍是200 - 请求失败的时候也是执行了期约的解决处理函数,只要服务器返回了响应,fetch()期约都会被解决
- 通常返回200认为是成功,可以在非200~299时检查Response对象的
ok
属性 - 因为服务器没有响应而导致浏览器超时,这样真正的fetch()失败会导致期约被拒绝
- 违反CORS、无网络连接、HTTPS错配以及其他浏览器/网络策略问题都会导致期约拒绝。
- 可以通过url属性检查通过fetch()发送请求时使用的完整URL
4、自定义选项
-
只使用URL时,fetch()会发送GET请求,只包含最低限度的请求头,第二个参数配置如下:
-
body
- 指定使用请求体时请求体的内容
- 必须是Blob、BufferSource、FormData、URLSearchParams、ReadableStream或String的实例
-
cache
- 用于控制浏览器与HTTP缓存的交互,要跟踪缓存的重定向,请求的redirect属性值必须是”follow”,必须符合同源策略限制,必须是下列值之一
- default(默认)
- fetch()返回命中的有效缓存,不发送请求
- 命中无效缓存会发送条件式请求。如果响应发生改变,则更新缓存值,然后fetch()返回缓存的值,
- 未命中缓存会发送请求,并缓存响应,然后fetch()返回响应
- no-store
- 浏览器不检查缓存,直接发送请求
- 不缓存响应,直接通过fetch()返回
- reload
- 浏览器不检查缓存,直接发送请求
- 缓存响应,在通过fetch()返回
- no-cache
- 无论命中有效缓存还是无效缓存都会发送条件式请求。如果响应已经改变,则更新缓存值,然后fetch()返回缓存的值,
- 未命中缓存会发送请求,并缓存响应,然后fetch()返回响应
- force-cache
- 无论命中有效缓存还是无效缓存都通过fetch()返回,不发送请求
- 未命中缓存会发送请求,并缓存响应,然后fetch()返回响应
- only-if-cached
- 只在请求模式为same-origin时使用缓存
- 无论命中有效缓存还是无效缓存都通过fetch()返回,不发送请求
- 未命中缓存返回状态码为504的响应
- default(默认)
- 用于控制浏览器与HTTP缓存的交互,要跟踪缓存的重定向,请求的redirect属性值必须是”follow”,必须符合同源策略限制,必须是下列值之一
-
credentilas
- 指定在外发送请求中如何包含cookie,类似
withCredentials
标签- omit
- 不发送cookie
- same-origin (默认)
- 只在请求URL与发送fetch()请求的页面同源时发送cookie
- include
- 无论同源还是跨域都包含cookie
- omit
- 指定在外发送请求中如何包含cookie,类似
-
headers
- 用于指定请求头部
- 必须是Headers对象实例或者包含字符串格式键值对的常规对象
- 默认值为不包含键值对的Headers对象,但请求会发送不可见的默认头部。
- 用于指定请求头部
-
integrity
- 强制子资源完整性
- 必须是包含子资源完整性标志符的字符串
- 默认空字符串
-
keepalive
- 指示浏览器允许请求存在时间超出页面声明周期。适合报告事件或分析。
- 布尔值,默认false
-
method
- 指定HTTP请求方法
- GET 默认
- POST
- PUT
- PATCH
- DELETE
- HEAD
- OPTIONS
- CONNECT
- TARCE
- 指定HTTP请求方法
-
mode
- 指定请求模式,决定来自跨源请求的响应是否有效,客户端可以读取多少响应。
- cors
- 允许遵循CORS协议的跨源请求
- 响应的是“CORS过滤的响应”,即响应中可以访问的浏览器头部是经过浏览器强制白名单过滤的。
- 手动创建Request实例时的默认值
- no-cors
- 允许不需要发送预检请求的跨源请求(HEAD、GET和织带有满足CORS请求头部的POST)
- 响应的类型是opaque,即不能读取响应内容
- 非手动创建Request实例时的默认值
- same-origin
- 任何跨源请求都不允许发送
- navigate
- 用于支持HTML导航,只在文档间导航使用
- cors
- 指定请求模式,决定来自跨源请求的响应是否有效,客户端可以读取多少响应。
-
redirect
- 用于指定如何重定向响应
- 状态码为301、302、303、307或308
- follow(默认)
- 跟踪重定向请求,以最终非重定向URL的响应作为最终响应
- error
- 重定向请求会抛出错误
- manual
- 不跟踪重定向,而是返回opaquerrdirect类型的响应
- 同时仍然暴露期望的重定向URL
- 允许以手动方式跟踪重定向
- follow(默认)
-
referrer
- 用于指定HTTP的Referer头部的内容
- no-referrer
- 以no-referrer作为值
- client/about:client (默认)
- 以当前URL或no-referrer(取决于来源策略referrerPolicy)作为值
- <URL>
- 以伪造URL作为值,伪造URL的源必须与执行脚本的源匹配
- no-referrer
- 用于指定HTTP的Referer头部的内容
-
referrerPolicy
- 用于指定HTTP的Referer头部
- no-referrer
- 请求中不包含Referer头部
- no-referrer-when-downgrade(默认)
- 对于从安全HTTPS上下文发送到HTTP URL的请求,不包含Referer头部
- 对于所有其他请求,将Referer设置为完整URL
- origin
- 对于所有请求,将Referer设置为包含源
- same-origin
- 对于跨源请求,不包含Referer头部
- 对于同源请求,将Referer设置为完整URL
- strict-origin
- 对于从安全HTTPS上下文发送到HTTP URL的请求,不包含Referer头部
- 对于所有其他请求,将Referer设置为只包含源
- origin-when-cross-origin
- 对于跨源请求,将Referer设置为只包含源
- 对于同源请求,将Referer设置为完整URL
- strict-origin-when-cross-origin
- 对于从安全HTTPS上下文发送到HTTP URL的请求,不包含Referer头部
- 对于所有其他跨源请求,将Referer设置为只包含源
- 对于同源请求,将Referer设置为完整URL
- unsafe-url
- 对于所有请求,将Referer设置为完整URL
- no-referrer
- 用于指定HTTP的Referer头部
-
signal
- 用于支持AbortController中断进行中的fetch()请求
- 必须是AbortSignal的实例
- 默认为为关联控制器的AbortSignal的实例
2、常见的Fetch请求模式
1、发送JSON数据
let payload = JSON.stringify({
foo: 'bar'
});
let jsonHeaders = new Headers({
'Content-Type': 'application/json'
});
fetch('/send-me-json', {
method: 'POST', // 发送请求体时必须使用一种 HTTP 方法
body: payload,
headers: jsonHeaders
})
复制代码
2、在请求体内发送参数
- 请求体支持任意字符串值,可以通过它发送请求参数
let payload = 'foo=bar&baz=qux';
let paramHeaders = new Headers({
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
});
fetch('/send-me-params', {
method: 'POST', // 发送请求体时必须使用一种 HTTP 方法
body: payload,
headers: paramHeaders
});
复制代码
3、发送文件
- 请求体支持FormData实现,所以fetch()也可以序列化并发送文件字段的文件
let imageFormData = new FormData();
let imageInput = document.querySelector("input[type='file']");
imageFormData.append('image', imageInput.files[0]);
fetch('/img-upload', {
method: 'POST',
body: imageFormData
});
// 这个 fetch() 实现可以支持多个文件:
let imageFormData = new FormData();
let imageInput = document.querySelector("input[type='file'][multiple]");
for (let i = 0; i < imageInput.files.length; ++i) {
imageFormData.append('image', imageInput.files[i]);
}
fetch('/img-upload', {
method: 'POST',
body: imageFormData
});
复制代码
4、加载Blob文件
- Fetch API能提供Blob类型的响应,而Blob可以兼容多种浏览器API。
- 常见做法是明确将图片文件加载到内存,然后将其添加到HTML图片元素。
- 可以使用响应对象暴露的blob方法。该方法返回一个期约,解决为一个Blob的实例。然后可以将这个实例传给
URL.createObjectUrl()
以生成可以添加图片元素src属性的值。
const imageElement = document.querySelector('img');
fetch('my-image.png')
.then((response) => response.blob())
.then((blob) => {
imageElement.src = URL.createObjectURL(blob);
});
复制代码
5、发送跨源请求
- 响应头要包含CORS头部才能保证浏览器收到响应
- 如果代码不需要访问请求,也可以发送no-cors请求。此时响应的type属性为opaque,因此无法读取响应内容。这中方式适合发送探测请求或将响应缓存起来供以后使用。
fetch('//cross-origin.com', { method: 'no-cors' })
.then((response) => console.log(response.type));
// opaque
复制代码
6、中断请求
- Fetch API 支持通过 AbortController/AbortSignal 对中断请求。
- 调用 AbortController. abort()会中断所有网络传输,特别适合希望停止传输大型负载的情况。中断进行中的 fetch()请求会 导致包含错误的拒绝
let abortController = new AbortController();
fetch('wikipedia.zip', { signal: abortController.signal })
.catch(() => console.log('aborted!');
// 10 毫秒后中断请求
setTimeout(() => abortController.abort(), 10);
// 已经中断
复制代码
3、Headers对象
-
外发请求和入站响应头部的容器。
-
可以通过Request.prototype.headers访问
-
new Headers() 可以创建新实例
1、Headers与Map相似之处
- 都有get()、set()、has()、delete()等方法
- 都可以用一个可迭代对象初始化
- 都有相同的keys()、values()、和entries()迭代器接口
2、Headers独有的特性
- 可以用键值对的形式初始化
- 支持使用
append()
方法添加多个值,在不存在的头部上调用相当于调用set(),后续调用会以逗号分隔符拼接
3、头部护卫
- 并非所有HTTP头部都可以被客户端修改,使用护卫防止不被允许的修改。不同护卫设置会改变set、append、delete行为,违反护卫限制会抛出TypeError
- 会因来源不同而展示不同行为,其行为由护卫控制。
护卫 | 情形 | 限制 |
---|---|---|
none | 在通过构造函数创建 Headers 实例时激活 | 无 |
request | 在通过构造函数初始化 Request对象,且 mode 值为非 no-cors 时激活 | 不允许修改禁止修改的头部 |
request-no-cors | 在通过构造函数初始化 Request对象,且 mode | 不允许修改非简单头部 |
response | 在通过构造函数初始化 Response 对象时激活 | 不允许修改禁止修改的响应头部 |
immutable | 在通过 error()或 redirect()静态方法初始 化 Response 对象时激活 | 不允许修改任何头部 |
4、Request 对象
- 获取资源请求的接口,暴露了请求的相关信息,也暴露了使用请求体的不同方式
1、创建Request对象
-
构造函数初始化
let r = new Request('https://foo.com'); 复制代码
-
接收第二个参数,没有init对象中涉及的值会用默认值
// 用所有默认值创建 Request 对象 console.log(new Request('')); // Request { // bodyUsed: false // cache: "default" // credentials: "same-origin" // destination: "" // headers: Headers {} // integrity: "" // keepalive: false // method: "GET" // mode: "cors" // redirect: "follow" // referrer: "about:client" // referrerPolicy: "" // signal: AbortSignal {aborted: false, onabort: null} // url: "<current URL>" // } 复制代码
-
用指定的初始值创建
2、克隆Request()对象
-
使用构造函数
let r1 = new Request('https://foo.com'); let r2 = new Request(r1); console.log(r2.url); // https://foo.com/ 复制代码
- 传入init对象会覆盖同名的值
- 不能得到一摸一样的副本,第一个请求的请求体会被标记为“已使用” (bodyUsed)
- 如果源对象与创建的新对象不同源,则referrer属性会被清除,如果源对象mode为navigate则会转为same-origin
-
clone()
方法- 创建一摸一样的副本,任何值不会覆盖,不会标记为已使用
let r1 = new Request('https://foo.com', { method: 'POST', body: 'foobar' }); let r2 = r1.clone(); console.log(r1.url); // https://foo.com/ console.log(r2.url); // https://foo.com/ console.log(r1.bodyUsed); // false console.log(r2.bodyUsed); // false 复制代码
- bodyUsed属性为true的对象,不能再创建这个对象的副本
let r = new Request('https://foo.com'); r.clone(); new Request(r); // 没有错误 r.text(); // 设置 bodyUsed 为 true r.clone(); // TypeError: Failed to execute 'clone' on 'Request': Request body is already used new Request(r); // TypeError: Failed to construct 'Request': Cannot construct a Request with a Request object that has already been used. 复制代码
3、在fetch()中使用Request对象
- 在调用fetch()时,可以传入已经创建好的Request实例。
- 传给fetch()的init对象会覆盖传入请求对象的值
let r = new Request('https://foo.com');
// 向 foo.com 发送 GET 请求
fetch(r);
// 向 foo.com 发送 POST 请求
fetch(r, { method: 'POST' });
复制代码
- fetch()会在内部克隆传入的Request对象,不能拿请求体已用过的Request对象来发送请求
- 由请求体的Request只能在一次fetch中使用
- 要想多次调用,需要在发送请求前调用
clone()
let r = new Request('https://foo.com',
{ method: 'POST', body: 'foobar' });
// 3 个都会成功
fetch(r.clone());
fetch(r.clone());
fetch(r);
复制代码
5、Response 对象
- 获取资源响应的接口,暴露了响应的相关信息,也暴露了使用响应体的不同方式
1、创建Response对象
-
构造函数创建,且不需要参数,属性均为默认值。
let r = new Response(); console.log(r); // Response { // body: (...) // bodyUsed: false // headers: Headers {} // ok: true // redirected: false // status: 200 // statusText: "OK" // type: "default" // url: "" // } 复制代码
-
接收一个可选的body参数,该参数可以是null。等同于fetch()参数init中的body。
-
还可以接收一个可选的init对象
- headers
- 必须是 Headers 对象实例或包含字符串键/值对的常规对象实例
- 默认为没有键/值对的 Headers 对象
- status
- status 表示 HTTP 响应状态码的整数
- 默认为 200
- statusText
- 表示 HTTP 响应状态的字符串
- 默认为空字符串
- headers
-
生成Response对象主要是调用fetch(),它返回一个最后会解决为Response对象的期约,这个Response对象代表实际的HTTP响应
-
Response类还有两个用于生成Response对象的方法
- Response.redirect()
- 前者接收一个 URL 和一个重定向状态码(301、302、303、307 或 308)
- 返回重定向的 Response 对象
- 提供的状态码必须对应重定向,否则会抛出错误
- Response. error()
- 用于产生表示网络错误的 Response 对象(网络错误会导致 fetch()期约被拒绝)
- Response.redirect()
2、读取响应状态信息
- Response 对象包含一组只读属性,描述了请求完成后的状态
headers
- 响应包含的Headers对象
ok
- 布尔值,表示HTTP状态码的含义 200~299 的状态码返回 true,其他状态码返回 false
redirected
- 布尔值,表示响应是否至少经过一次重定向
status
- 整数,表示响应的 HTTP 状态码
statusText
- 字符串,包含对 HTTP 状态码的正式描述。
- 这个值派生自可选的 HTTP Reason-Phrase 字段,因此如果服 务器以 Reason-Phrase 为由拒绝响应,这个字段可能是空字符串
type
- 字符串,包含响应类型。
- basic:表示标准的同源响应
- cors:表示标准的跨源响应
- error:表示响应对象是通过 Response.error()创建的
- opaque:表示 no-cors 的 fetch()返回的跨源响应
- opaqueredirect:表示对 redirect 设置为 manual 的请求的响应
url
- 包含响应 URL 的字符串。
- 对于重定向响应,这是最终的 URL,非重定向响应就是它产生的
3、克隆Response对象
-
clone()
-
这个方法会创建一个一模一样的副本
-
不会覆盖任何值。这样不会将任何请求的请求体标记为已使用
-
如果响应对象的 bodyUsed 属性为 true(即响应体已被读取),则不能再创建这个对象的副本。在 响应体被读取之后再克隆会导致抛出 TypeError
-
有响应体的 Response 对象只能读取一次。(不包含响应体的 Response 对象不受此限制。)
-
要多次读取包含响应体的同一个 Response 对象,必须在第一次读取前调用 clone():
-
通过创建带有原始响应体的 Response 实例,可以执行伪克隆操作。
- 关键是这样不会把第一 个 Response 实例标记为已读,而是会在两个响应之间共享
let r1 = new Response('foobar'); let r2 = new Response(r1.body); console.log(r1.bodyUsed); // false console.log(r2.bodyUsed); // false r2.text().then(console.log); // foobar r1.text().then(console.log); // TypeError: Failed to execute 'text' on 'Response': body stream is locked 复制代码
-
6、Request、Response及Body混入
- Request 和 Response 都使用了 Fetch API 的 Body 混入,以实现两者承担有效载荷的能力。
- 这个 混入为两个类型提供了只读的 body 属性(实现为 ReadableStream)、只读的 bodyUsed 布尔值(表 示 body 流是否已读)和一组方法,用于从流中读取内容并将结果转换为某种 JavaScript 对象类型。
- 作为流使用的原因:
- 有效载荷的大小可能会导致网络延迟
- 流API本身在处理有效载荷方面是有优势的
- 最好是一次性获取资源主体
- 在解决之前,期约会等待主体流报告完成及缓冲被解析。这意味着客户端必须等待响应的资源完全加载才能访问其内容。
1、Body.text()
- 返回期约,解决为缓冲区转存得到的UTF-8字符串
// 下面的代码展示了在 Response 对象上使用 Body.text():
fetch('https://foo.com')
.then((response) => response.text())
.then(console.log);
// <!doctype html><html lang="en">
// <head>
// <meta charset="utf-8">
// ...
//以下代码展示了在 Request 对象上使用 Body.text():
let request = new Request('https://foo.com',
{ method: 'POST', body: 'barbazqux' });
request.text()
.then(console.log);
// barbazqux
复制代码
2、Body.json()
- 返回期约,解决为将缓冲区转存得到的JSON
// 下面的代码展示了在 Response对象上使用 Body.json():
fetch('https://foo.com/foo.json')
.then((response) => response.json())
.then(console.log);
// {"foo": "bar"}
// 以下代码展示了在 Request 对象上使用 Body.json():
let request = new Request('https://foo.com',
{ method:'POST', body: JSON.stringify({ bar: 'baz' }) });
request.json()
.then(console.log);
// {bar: 'baz'}
复制代码
3、Body.formData()
- 浏览器可以将 FormData 对象序列化/反序列化为主体
- Body.formData()方法返回期约,解决为将缓冲区转存得到的 FormData 实例
// 下面的代码展示了在 Response 对象上使用 Body.formData():
fetch('https://foo.com/form-data')
.then((response) => response.formData())
.then((formData) => console.log(formData.get('foo'));
// bar
// 以下代码展示了在 Request 对象上使用 Body.formData():
let myFormData = new FormData();
myFormData.append('foo', 'bar');
let request = new Request('https://foo.com',
{ method:'POST', body: myFormData });
request.formData()
.then((formData) => console.log(formData.get('foo'));
// bar
复制代码
4、 Body.arrayBuffer()
- 返回期约,解决为将缓冲区转存得到 的 ArrayBuffer 实例
// 下面的代码展示了在 Response 对象上使用 Body.arrayBuffer():
fetch('https://foo.com')
.then((response) => response.arrayBuffer())
.then(console.log);
// ArrayBuffer(...) {}
// 以下代码展示了在 Request 对象上使用 Body.arrayBuffer():
let request = new Request('https://foo.com',
{ method:'POST', body: 'abcdefg' });
// 以整数形式打印二进制编码的字符串
request.arrayBuffer()
.then((buf) => console.log(new Int8Array(buf)));
// Int8Array(7) [97, 98, 99, 100, 101, 102, 103]
复制代码
5、Body.blob()
- 返回期约,解决为将缓冲区转存得到的 Blob 实例
// 下面的代码展示了在 Response 对象上使用 Body.blob():
fetch('https://foo.com')
.then((response) => response.blob())
.then(console.log);
// Blob(...) {size:..., type: "..."}
// 以下代码展示了在 Request 对象上使用 Body.blob():
let request = new Request('https://foo.com',
{ method:'POST', body: 'abcdefg' });
request.blob()
.then(console.log);
// Blob(7) {size: 7, type: "text/plain;charset=utf-8"}
复制代码
6、一次性流
-
Body 混入是构建在 ReadableStream 之上的,所以主体流只能使用一次。
-
这意味着所有主 体混入方法都只能调用一次,再次调用就会抛出错误
-
作为 Body 混入的一部分,bodyUsed 布尔值属性表示 ReadableStream 是否已摄受
7、使用 ReadableStream 主体
- 从 TCP/IP 角度来看,传输的数据是以分块形式抵达端点的,而且速度受到网速的限制。
- 接收端点 会为此分配内存,并将收到的块写入内存。
- Fetch API 通过 ReadableStream 支持在这些块到达时就实时读取和操作这些数据。
- ReadableStream暴露了
getReader()
方法,用于产生ReadableStreamDefaultReader,这个读取器可以用于在数据到达时异步获取数据块。数据流的格式是 Uint8Array。
fetch('https://fetch.spec.whatwg.org/')
.then((response) => response.body)
.then((body) => {
let reader = body.getReader();
console.log(reader); // ReadableStreamDefaultReader {}
reader.read()
.then(console.log);
});
// { value: Uint8Array{}, done: false }
复制代码
- 在随着数据流的到来取得整个有效载荷,可以递归调用 read()方法
fetch('https://fetch.spec.whatwg.org/')
.then((response) => response.body)
.then((body) => {
let reader = body.getReader();
function processNextChunk({
value,
done
}) {
if (done) {
return;
}
console.log(value);
return reader.read()
.then(processNextChunk);
}
return reader.read()
.then(processNextChunk);
});
// { value: Uint8Array{}, done: false }
// { value: Uint8Array{}, done: false }
// { value: Uint8Array{}, done: false }
// ...
复制代码
- 异步函数非常适合这样的 fetch()操作。可以通过使用 async/await 将上面的递归调用打平
fetch('https://fetch.spec.whatwg.org/')
.then((response) => response.body)
.then(async function (body) {
let reader = body.getReader();
while (true) {
let {
value,
done
} = await reader.read();
if (done) {
break;
}
console.log(value);
}
});
// { value: Uint8Array{}, done: false }
// { value: Uint8Array{}, done: false }
// { value: Uint8Array{}, done: false }
// ...
复制代码
- read()方法也可以真接封装到 Iterable 接口中。因此就可以在 for-await-of 循环中方 便地实现这种转换
fetch('https://fetch.spec.whatwg.org/')
.then((response) => response.body)
.then(async function(body) {
let reader = body.getReader()
let asyncIterable = {
[Symbol.asyncIterator]() {
return {
next() {
return reader.read()
}
}
}
}
for await (chunk of asyncIterable) {
console.log(chunk)
}
})
// { value: Uint8Array{}, done: false }
// { value: Uint8Array{}, done: false }
// { value: Uint8Array{}, done: false }
// ...
复制代码
- 进一步将异步逻辑包装到生成器函数
async function* streamGenerator(stream) {
const reader = stream.getReader()
try {
while(true) {
const { value, done } = await reader.read()
if(done) {
break
}
yield value
}
} finally {
reader.releaseLock()
}
}
fetch('https://fetch.spec.whatwg.org/')
.then((response) => response.body)
.then(async function(body) {
for await (chunk of streamGenerator(body)) {
console.log(chunk)
}
})
复制代码
- 当读取完 Uint8Array 块之后,浏览器会将其标记为可以被垃圾回收。对于需要在 不连续的内存中连续检查大量数据的情况,这样可以节省很多内存空间
- 缓冲区的大小,以及浏览器是否等待缓冲区被填充后才将其推到流中,要根据 JavaScript 运行时的实现
- 不同浏览器中分块大小可能不同,取决于带宽和网络延迟,不同情况
- 不同大小的 Unit8Array块
- 部分填充的Unit8Array块
- 块到达的时间间隔不确定
- Unit8Array转为可读文本,通过TextDecoder,返回转换后的值。通过设 置 stream: true,可以将之前的缓冲区保留在内存,从而让跨越两个块的内容能够被正确解码
let decoder = new TextDecoder();
async function* streamGenerator(stream) {
const reader = stream.getReader();
try {
while (true) {
const {
value,
done
} = await reader.read();
if (done) {
break;
}
yield value;
}
} finally {
reader.releaseLock();
}
}
fetch('https://fetch.spec.whatwg.org/')
.then((response) => response.body)
.then(async function (body) {
for await (chunk of streamGenerator(body)) {
console.log(decoder.decode(chunk, {
stream: true
}));
}
});
复制代码
- 因为可以使用 ReadableStream 创建 Response 对象,所以就可以在读取流之后,将其通过管道导入另一个流。
- 然后在这个新流上再使用 Body 的方法,如 text()。这样就可以随着流的到达实时检查和操作流内容
fetch('https://fetch.spec.whatwg.org/')
.then((response) => response.body)
.then((body) => {
const reader = body.getReader();
// 创建第二个流
return new ReadableStream({
async start(controller) {
try {
while (true) {
const {
value,
done
} = await reader.read();
if (done) {
break;
}
// 将主体流的块推到第二个流
controller.enqueue(value);
}
} finally {
controller.close();
reader.releaseLock();
}
}
})
})
.then((secondaryStream) => new Response(secondaryStream))
.then(response => response.text())
.then(console.log);
复制代码
6、Beacon API
- 解决页面销毁时unload事件无法发送异步请求。
navigator.sendBeacon()
接收一个URL一个数据的有效载荷参数,发送POST请求。- 可选的数据有效载荷参数有 ArrayBufferView、Blob、DOMString、FormData 实例。
- 如果请 求成功进入了最终要发送的任务队列,则这个方法返回 true,否则返回 false。
// 发送 POST 请求
// URL: 'https://example.com/analytics-reporting-url'
// 请求负载:'{foo: "bar"}'
navigator.sendBeacon('https://example.com/analytics-reporting-url', '{foo: "bar"}');
复制代码
- sendBeacon() 任何时候都可以使用
- sendBeacon()后,浏览器会把请求添加到一个内部的请求队列。浏览器会主动地发送队 列中的请求。
- 浏览器保证在原始页面已经关闭的情况下也会发送请求
- 状态码、超时和其他网络原因造成的失败完全是不透明的,不能通过编程方式处理
- 信标(beacon)请求会携带调用 sendBeacon()时所有相关的 cookie
7、Web Socket
- 目标是通过一个长时连接实现与服务器全双工、双向的通信
- 在 JavaScript 中创建 Web Socket 时,一个 HTTP 请求会发送到服务器以初始化连接。
- 服务器响应后,连接使用 HTTP 的 Upgrade 头部从 HTTP 协议切换到 Web Socket 协议。
- 这意味着 Web Socket 不能通过标准 HTTP服务器实现,而必须使用支持该协议的专有服务器。
- 要使用 ws://和 wss://。前者是不安全的连接,后者是安全连接。
- 客户端与服务器之间可以发送非常少的数据,不会对 HTTP 造成任何负担。
1、API
- 创建必须传入绝对URL
- 同源策略不适用于 Web Socket,因此可以 打开到任意站点的连接。至于是否与来自特定源的页面通信,则完全取决于服务器
let socket = new WebSocket("ws://www.example.com/server.php");
复制代码
- 浏览器会在初始化 WebSocket 对象之后立即创建连接。与 XHR 类似,WebSocket 也有一个 readyState 属性表示当前状态。
- WebSocket.OPENING(0):连接正在建立。
- WebSocket.OPEN(1):连接已经建立。
- WebSocket.CLOSING(2):连接正在关闭。
- WebSocket.CLOSE(3):连接已经关闭。
- 任何时候都可以调用 close()方法关闭 Web Socket 连接,
socket.close()
复制代码
- 调用 close()之后,readyState 立即变为 2(连接正在关闭),并会在关闭后变为 3(连接已经关闭)。
2、发送和接收数据
- 通过连接发送和接收数据,要向服务器发送数据,使用
send()
方法传入字符串、ArrayBuffer 或 Blob
let socket = new WebSocket("ws://www.example.com/server.php");
let stringData = "Hello world!";
let arrayBufferData = Uint8Array.from(['f', 'o', 'o']);
let blobData = new Blob(['f', 'o', 'o']);
socket.send(stringData);
socket.send(arrayBufferData.buffer);
socket.send(blobData);
复制代码
- 服务器向客户端发送消息时,WebSocket 对象上会触发
message
事件。 - 这个 message 事件与其 他消息协议类似,可以通过
event.data
属性访问到有效载荷
socket.onmessage = function(event) {
let data = event.data;
// 对数据执行某些操作
};
复制代码
event.data
返回的数据也可能是 ArrayBuffer 或 Blob。 这由 WebSocket 对象的binaryType
属性决定,该属性可能是”blob”或”arraybuffer”
3、其他事件
- open:在连接成功建立时触发。
- error:在发生错误时触发。连接无法存续。
- close:在连接关闭时触发。
let socket = new WebSocket("ws://www.example.com/server.php");
socket.onopen = function () {
alert("Connection established.");
};
socket.onerror = function () {
alert("Connection error.");
};
socket.onclose = function () {
alert("Connection closed.");
};
复制代码
- 只有 close 事件的 event 对象上有额外信息。这个对象上有 3 个额外属性
- wasClean 布尔值,表示连接是否干净地关闭
- code 来自服务器的数值状态码
- reason 字符串,包含服务器发来的消息
socket.onclose = function (event) {
console.log(`as clean? ${event.wasClean} Code=${event.code} Reason=${event.reason}`);
};
复制代码
8、安全
- 任何 Ajax 可以访问的 URL,也可以通过浏览器或服务器访问
- 在未授权系统可以访问某个资源时,可以将其视为跨站点请求伪造(CSRF)攻击
- 未授权系统会按照处理请求的服务器的要求伪装自己。
- Ajax 应用程序,无论大小,都会受到 CSRF 攻击的影响,包括无害的漏洞验证攻击和恶意的数据盗窃或数据破坏攻击
- 措施
- 要求通过 SSL 访问能够被 Ajax 访问的资源
- 要求每个请求都发送一个按约定算法计算好的令牌(token)
- 注意,以下手段对防护 CSRF 攻击是无效的
- 要求 POST 而非 GET 请求(很容易修改请求方法)
- 使用来源 URL 验证来源(来源 URL 很容易伪造)
- 基于 cookie 验证(同样很容易伪造)