XMLHttpRequest API 详细介绍

发布于:2024-08-12 ⋅ 阅读:(130) ⋅ 点赞:(0)

XMLHttpRequest API 详细介绍

XMLHttpRequest 标准定义了一个API,它提供了用于在客户端和服务器之间传输数据的脚本客户端功能。

open

语法:

open(method: string, url: string | URL): void;
open(method: string, url: string | URL, async: boolean, username?: string | null, password?: string | null): void;

可以用来设置请求方法,请求 url 以及同步标志。

method

在浏览器中不支持使用 connect、trace、track 方法,会报错。

Uncaught DOMException: Failed to execute 'open' on 'XMLHttpRequest': 'connect' HTTP method is unsupported.

其他非规范的 http 请求方法也会报错。

method 字段大小写不敏感,可以使用大写、小写都行,在最终发送请求的时候会转换成大写。

对于跨域请求,安全的请求方法是 GET、HEAD 或 POST 方法,对于 PUT、DELETE 要单独在响应头中配置 Access-Control-Allow-Methods,否则即使配置了 Access-Control-Allow-Origin,依旧会出现跨域错误。

url

url 可以有两种形式:string 或者 URL 实例:

// string 形式
open('get', 'http://localhost:3000/');

// URL 实例
open('get', new URL('http://localhost:3000/'))

当设置的请求地址无法解析时会报错,比如:

请添加图片描述

async

默认为 true,表示是异步请求,可以通过成 false 来实现同步。

const xhr = new XMLHttpRequest();
xhr.open('get', 'http://localhost:3000/', false);
xhr.send();

console.log('log until request done')

因为同步请求对最终的用户体验会产生不利影响,规范已经移除对 worker 进程以外的同步请求的支持,但是浏览器仍旧支持,不过对同步请求有一些限制:

  • 当当前的全局对象为 window 时,不允许设置 timeout 属性;

    报错示例:

    Uncaught DOMException: Failed to execute 'open' on 'XMLHttpRequest': Synchronous requests must not set a timeout.
    
  • 当当前的全局对象为 window 时,responseType 必须是空字符串;

    报错示例:

    Uncaught DOMException: Failed to execute 'open' on 'XMLHttpRequest': Synchronous requests from a document must not set a response type.
    

可以从上面的两条限制可以看到,都是针对于浏览器的主线程,在 web worker 中其实是可以随意使用同步请求的。

username、password

可以用于设置用户名和密码。在某些情况下,有些 URL 需要用户名和密码才能进行访问,通过在 username、password 设置,在请求的时候会自动放在 host 前面。

比如:

xhr.open('get', 'http://localhost:3000/', false, 'leo', 'password');

// 此时,浏览器发送的请求为:
// http://leo:password@localhost:3000/

setRequestHeader

语法:

setRequestHeader(name, value)

添加一个新的请求头或者合并同一个请求头的值。

当 state 并非 OPENED 或者 send() 已经调用但是还没有响应时(当请求完成后是可以调用的)调用 setRequestHeader 方法就会报错。

报错示例:

Uncaught DOMException: Failed to execute 'setRequestHeader' on 'XMLHttpRequest': The object's state must be OPENED.

重复设置同一个请求头

当使用 setRequestHeader 重复设置同一个请求头时,并不是修改原来的值,而是进行合并:

xhr.setRequestHeader('X-Test', 'one');
xhr.setRequestHeader('X-Test', 'two');

// X-Test: one, two

timeout

可以设置、读取超时时间(毫秒)。

当设置的值为非 0 时,请求会在指定时间内终止掉还未完成的请求(注意,这里的终止指的是并不是取消请求的发送,而是不接收响应,请求一旦发出就没办法撤回,但是可以选择不接收。)。

即使已经调用了 send() 方法发送了请求,还是可以设置 timeout 的,并不会报错,同时也是从请求开始发送的时刻开始计时。

// setter
xhr.timeout = 3000

// getter
xhr.timeout  // 3000
  • 当设置的值为非数字时,浏览器会默认修改为 0;
  • 当设置的值为负数时,浏览器会默认修改为 4294967295;
  • 当设置的值时纯数值字符串时,会先转换成 number 类型,在根据正负数重新判断第二点。
xhr.timeout = false;
xhr.timeout;     // 0

xhr.timeout = -1;
xhr.timeout;     // 4294967295

xhr.timeout = '3000';
xhr.timeout;     // 3000

xhr.timeout = '-1';
xhr.timeout;     // 4294967295

xhr.timeout = 'string';
xhr.timeout;     // 0

withCredentials

可以设置、读取 withCredentials 的值。

withCredentials 属性用于指示跨域请求时是否需要携带跨域请求的凭据(如 Cookies 和 HTTP 认证信息)。

对于同域请求,如果响应里存在 Set-Cookie 头,那么后续的请求会自动添加上 Cookie 这个请求头。在默认情况下,浏览器会阻止跨域请求携带凭据(也就是不会自动携带 Cookie 字段,而 Cookie 这个请求头是不允许手动添加的,因此在请求头中不会有 Cookie 的存在),以保护用户的隐私和安全。

这个属性的出现主要是为了解决跨域请求时的安全性问题。在有些情况下,确实需要在跨域请求中携带凭据,比如在单点登录系统中,用户在一个域名下登录后,希望在另一个域名下也能够保持登录状态,这时就需要通过设置 withCredentials 为 true 来允许携带凭据。

除了单点登录系统外,还有一些其他常见的使用场景,例如在前后端分离的项目中,前端页面可能托管在一个域名下,而后端 API 可能托管在另一个域名下,这时如果需要在跨域请求中传递用户的认证信息(比如 JWT Token),就需要将 withCredentials 设置为 true。

// 在跨域请求时携带上当前域中的 cookie
xhr.withCredentials = true;

此时在服务端需要添加额外的请求头

// node 实现

res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:8080');
res.setHeader('Access-Control-Allow-Credentials', true);

Access-Control-Allow-Origin 必须要是明确的IP来源 , 不能是通配符 * , 它会被视为此次的 HTTP 不安全。

浏览器报错示例:

The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

Access-Control-Allow-Credentials 也必须设置成 true,否则同样会提示跨域问题。

浏览器报错示例:

The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

在设置 Set-Cookie 的时候,也需要添加 SameSite=None;Secure:

res.setHeader(
    'Set-Cookie',
    'name=fakeCookie; Max-Age=3600;SameSite=None;Secure'
);

不然这个 cookie 还是不会携带到跨域请求中。

SameSite 可以设置为三个属性 strict,Lax,None:

strict:该属性表示表示完全禁止第三方cookie,也就是在跨域时,均不会携带cookie,只有当前站点的 url 和访问的站点的 url 一致时,才能携带 cookie。

但是我们此时想一种情况,比如说当前站点 A 存在一个链接,链接到 B 网站,如果我们之前已经登录了 B 网站的话,则我们再次访问该网站时应该是处于登录状态的。但是我们对当前站点 cookie 设置了 SameSite 属性为 strict 值,所以当前跳转链接并不会携带 cookie,所以我们的信息无法得到认证,此时就需要重新登录。

Lax:该属性比 strict 的属性要宽松一些,其允许我们在跨站使用 get 请求时携带cookie(重定向和window.open 是get请求)。

None:chrome 将 Lax 设置为默认值,此时我们可以更改 samesite 的值,将其设置为 none,此时必须同时设置 Secure 属性(此时 Cookie 只能通过 HTTPS 协议发送),否则无效。

send

语法:

send([body = null])

这个方法才会初始化请求。如果没有先调用 open() 方法,会报错。

报错示例:

Failed to execute 'send' on 'XMLHttpRequest': The object's state must be OPENED.

在请求还未完成时再次调用 send() 方法也会报错,报错同上。

如果请求方法是 GET 或者 HEAD,默认把 body 设置为 null。如果没有使用 setRequestHeader() 方法设置 Accept 头部信息,那么默认设置 Accept 为 /

在 XHR 请求中要发送的数据体。可以是:

  • Document, 在这种情况下,它在发送之前被序列化。当发送一个 Document 对象时,Firefox 3 之前的版本都是使用 UTF-8 编码发送请求的;FireFox 3 则使用由 body.xmlEncoding 指定的编码格式正确的发送文档,但如果未指定编码格式,则使用 UTF-8 编码格式发送。
  • Blob, BufferSource, FormData, URLSearchParams, 或者 USVString 对象,查看规范
  • null(默认值)

Content-Type:
string - 自动设置成 text/plain;charset=UTF-8

xhr.send('hi')

Blob - 没有自动设置,默认没有 Content-Type 这个请求头

xhr.send(new Blob('blob'))

Document - 自动设置成 text/html;charset=UTF-8

xhr.send(document)

FormData - 自动设置成 multipart/form-data; boundary= xxxx

const formdata = new FormData();
formdata.set('name', 'leo');
xhr.send(formdata)

URLSearchParams - 自动设置成 application/x-www-form-urlencoded;charset=UTF-8

const data = new URLSearchParams();
data.append('name', 'leo');
xhr.send(data);

abort

取消请求,同时会将 readyState 设置成 4 (请求已完成)。如果请求已经完成后再调用 abort() 方法,并不会生效,也不会报错。

abort 方法跟 timeout 同理,取消请求指的是并不是取消请求的发送,而是不接收响应,请求一旦发出就没办法撤回,但是可以选择不接收。

readyState

获取当前请求的状态,有4种:

  • 0(UNSENT):默认状态,创建了 XMLHttpRequest 实例,但是还没有任何操作
  • 1(OPENED): 成功调用了 open 方法,在这个状态下,可以使用 setRequestHeader 方法设置请求头,此时请求仍未发送
  • 2(HEADERS_RECEIVED):接收到响应头触发(如果是 redirect 请求,那么以接收到重新发送的请求的响应头才会触发)
  • 3(LOADING):接收到响应正文
  • 4(DONE):数据传输完成或者在传输过程中出现异常(跨域问题、调用 abort 方法)

可监听事件

有以下几种事件可以监听:

  • loadstart:初始化请求的时候触发,也就是调用 send 方法的时候
  • progress:当接收到数据的时候触发,比如说获取到响应头,在这个阶段 readyState 为 3
  • abort:当调用 abort 方法时触发
  • error:当请求异常时触发
  • load:当请求成功时触发
  • timeout:当请求超时时触发
  • loadend:当请求完成时或者在传输过程中出现异常时触发
  • readystatechange:当 readyState 状态改变时触发

可以通过在 XMLHttpRequest 实例中的 addEventListener(eventName) 方法监听;或者直接通过实例中的 on[eventName] 方法

const xhr = new XMLHttpRequest();
xhr.addEventListener('loaded', function() {})

xhr.onloadend = function() {}

status

响应状态码

statusText

响应状态码描述

responseType

可以设置接收到的响应正文的格式,有以下几种:

  • 空字符串(默认)
  • arraybuffer
  • blob
  • document
  • json
  • text

response

获取响应正文。

只有在 readyState 为 4 ,也就是请求成功且完成了的时候 reponse 才不为 null,其他情况都是为 null(比如在 readyState 不为 4 的时候就去读取 response 或者在 readyState为 4 但是请求异常时去读取)。

当设置的 responseType 为 arraybuffer,会把响应结果(一个对象,或者错误,或者 null)放入到 new ArrayBuffer 中,生成一个 arraybuffer 对象,如果在这个时候时候抛出异常,会将响应状态设置成失败并把响应结果设置成 null。

const xhr = new XMLHttpRequest();
xhr.responseType = 'arraybuffer';
xhr.onreadystatechange = function() {
  if(xhr.readystate === 4 && xhr.status === 200) {
    console.log(xhr.response);
  }
}

通常我们在封装 XHR 的时候都是监听 readystatechange 事件,并通过判断 readystate 的值是否为 4,以及处理 status 状态码。

当设置的 responseType 为 blob 时,响应对象会创建一个新的 Blob 示例,这个 blob 的类型是根据响应头中的 Content-Type 来确定的。如果调用了 overrideMimeType(type) 方法,那么设置的这个类型为准,覆盖掉响应头中的 Content-Type。如果两个都没有设置,默认为 text/xml。

// 默认是 text/xml 的情况:
xhr.response;
// Blob {size: 24, type: 'text/xml'}

// 响应头的Content-Type 为 text/javascript 的情况:
xhr.response;
// Blob {size: 24, type: 'text/javascript'}

// 调用了 overrideMimeType 方法的情况:
xhr.overrideMimeType('text/plain');
xhr.response;
// Blob {size: 24, type: 'text/plain'}

当设置的 responseType 为 document 时就需要注意,此时需要确保响应头中的 Content-Type 为 text/html。如果是 text/html,但是相应正文是非 html 格式的字符串,会被自动包装在 html 格式中,比如:

// 响应正文是 {code: 0, msg: 'success'}
// xhr.response 为
<html>
  <head></head>
  <body>{code: 0, msg: 'success'}</body>
</html>

如果是非 text/html,那么 response 的值会被设置为 null。此时接口请求虽然是成功的,但是浏览器并不会把响应正文放入到 response 中。

当设置的 responseType 为 json 时,如果响应正文不能通过 JSON.parse() 进行解析,那么 response 也是 null。此时接口请求虽然是成功的,但是浏览器并不会把响应正文放入到 response 中。

当设置的 responseType 为 text 时,将响应正文转换成 string 格式。

responseText

如果设置的 responseType 不为空字符串或者 text,那么在读取这个字段的时候会报错。

报错示例:

Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was 'json').

responseXML

如果设置的 responseType 不为空字符串或者 document,那么在读取这个字段的时候会报错。

报错示例:

Failed to read the 'responseXML' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'document' (was 'json').

网站公告

今日签到

点亮在社区的每一天
去签到