JavaScript – XMLHttpRequest

前言

XMLHttpRequest 是 JavaScript built-in 的一个 class,用于发送 HTTP 请求,俗称 AJAX。

这几年 XMLHttpRequest 已经逐渐被 Fetch 取代了,只剩下一个功能目前 Fetch 还做不到 -- 获取上传进度,因此 XMLHttpRequest 还是得学起来😑。

 

Simple Get Request & Response

首先实例化 XMLHttpRequest 对象

const request = new XMLHttpRequest();

接着设置 request method 和 URL

request.open('GET', 'https://192.168.1.152:44300/products');

接着监听 request state change 事件

request.addEventListener('readystatechange', () => {
    if (request.readyState === 4) {
      console.log('status', request.status);
      console.log('response', request.response);
    }
  });

request.readyState 表示 request 当前的状态,4 代表 response 已经下载完毕。通过 readystatechange event 可以监听到 request.readyState 的变化。

接着发送 request

request.send();

效果

 

Request with Query Parameters

XMLHttpRequest 没有 built-in 对 Query Parameters 的处理。

我们需要借助 URLSearchParams。

const request = new XMLHttpRequest();
const searchParams = new URLSearchParams({
  key1: 'value1',
});
const queryString = '?' + searchParams.toString();
request.open('GET', 'https://192.168.1.152:44300/products' + queryString);

然后把 Query String 拼接到 request URL 就可以了。

 

Request with Header

通过 request.setRequestHeader 方法就可以添加 header 了。

const request = new XMLHttpRequest();
request.open('GET', 'https://192.168.1.152:44300/products');
request.setRequestHeader('Accept', 'application/json');

 

Auto Parse JSON Response

上面的例子,request.response 是一个 string 类型,我们需要手动 parse JSON。

request.addEventListener('readystatechange', () => {
  if (request.readyState === 4) {
    // status 是 number 类型
    console.log('status', request.status);     // 200

    // response 是 string 类型
    console.log('response', request.response); // '[{"name":"iPhone14"},{"name":"iPhone15"}]'

    const products = JSON.parse(request.response) as { name: string }[];

    console.log(products[0].name); // 'iPhone14'
  }
});

我们可以通过设置 request.responseType 让 XMLHttpRequest 自动替我们 parse JSON。

request.responseType = 'json';
request.addEventListener('readystatechange', () => {
  if (request.readyState === 4) {
    // response 变成了 Array 类型
    const products: { name: string }[] = request.response;
    console.log(products[0].name); // 'iPhone14'
  }
});

 

Response Header

request.getResponseHeader 方法和 request.getAllResponseHeaders 方法

request.addEventListener('readystatechange', () => {
  if (request.readyState === 4) {
    console.log(request.getResponseHeader('Content-Type'));                // 'application/json; charset=utf-8'
    console.log('response content type', request.getAllResponseHeaders()); // 'content-type: application/json; charset=utf-8'
  }
});

 

Cross-Origin 跨域请求携带 Cookie

跨域主要是服务端的职责,不熟悉的可以看这篇 ASP.NET Core – CORS (Cross-Origin Resource Sharing)

客户端和跨域有关的是 Cookie,

它有两种情况:

  1. 请求同跨 (same-origin)
    默认情况下会携带 Cookie
    如果不希望携带 Cookie 可以设置
    request.withCredentials = false;
  2. 请求跨域 (cross-origin)
    默认情况下不携带 Cookie
    如果希望携带 Cookie 可以设置
    request.withCredentials = true;

Request Error

Request Error 指的是请求失败,通常是 offline 造成的。

status 400-5xx 这些可不算 Request Error 哦,因为这些已经是有 response 成功了,只是在 status 被区分是有问题的。

要处理 Request Error,监听 error 事件就可以了。

request.addEventListener('error', () => {
  console.log('Network error, possibly offline!');
});

注:request error 依然会发布 readystatechange 事件,request.readyState 依然等于 4,但是 status 会是 0。

 

Abort Request

我们可以在任意时间终止一个 request。

request.addEventListener('abort', () => {
  console.log('The request has been aborted.');
});

setTimeout(() => {
  request.abort();
}, 2000);

两秒钟后执行 request.abort 方法,终止请求,同时发布 abort 事件。

另外,客户端也会通知服务端,请求被终止了。

ASP.NET Core 有一个 cancellationToken,可以随时查看是否客户端已经终止请求, 如果已经终止,那就不需要继续执行了。

注:request 被 abort 了依然会发布 readystatechange 事件,request.readyState 依然等于 4,但是 status 会是 0。

 

Request Timeout

我们可以设置请求的最长时间,如果在时间内没有收到 response 或者 response body 来不及被下载完,这都算请求超时。

request.timeout = 5000; // unit 是 milliseconds
request.addEventListener('timeout', () => {
  console.log('Timeout, the request is taking more than five seconds.');
});

监听 timeout 事件可以处理超时请求。

注:request timeout 依然会发布 readystatechange 事件,request.readyState 依然等于 4,但是 status 会是 0。

 

Download File

除了请求 JSON 数据,偶尔我们也会需要下载文件。

download txt file

const request = new XMLHttpRequest();
request.open('GET', 'https://192.168.1.152:44300/data.txt');
request.responseType = 'arraybuffer';
request.addEventListener('readystatechange', () => {
  if (request.readyState === 4) {
    const memoryStream: ArrayBuffer = request.response;
    const bytes = new Uint8Array(memoryStream);
    const textDecoder = new TextDecoder();
    const text = textDecoder.decode(bytes);
    console.log(text); // 'Hello World'
  }
});
request.send();

关键是 responseType 设置成了 'arraybuffer'。

request.response 的类型变成了 ArrayBuffer,通过 Uint8Array 和 TextDecoder 从 ArrayBuffer 读取 data.txt 的内容。

Download Video

Video 通常 size 比较大,用 ArrayBuffer 怕内存会不够,所以用 Blob 会比较合适。

const request = new XMLHttpRequest();
request.open('GET', 'https://192.168.1.152:44300/video.mp4');
request.responseType = 'blob';
request.addEventListener('readystatechange', () => {
  if (request.readyState === 4) {
    const blob: Blob = request.response;
    console.log(blob.size / 1024); // 124,645 kb
    console.log(blob.type); // video/mp4
  }
});
request.send();

download progress

文件大下载慢,最好可以显示进度条

request.addEventListener('progress', e => {
  const percentage = ((e.loaded / e.total) * 100).toFixed() + '%';
  console.log(percentage);
});

通过监听 progress 事件,可以获取到 total size 和已下载的 size。progress 事件会触发多次。

partial data on downloading

如果把 responseType 设置成 'text',request.response 会在 progress 的过程中逐渐被添加。

request.responseType = 'text';
request.addEventListener('progress', () => {
  console.log(request.response);
});

只有 'text' 才会这样,'blob', 'arraybuffer' 在 progress 阶段 request.response 始终是 null。

所以,这个功能其实没有什么鸟用,因为如果我们请求的是一个 video.mp4,使用 ‘text’ 的话,video 原本的 binary 会被强制转换成 string,而这个过程很可能会破坏掉原本的 binary,导致 binary 无可还原,video 就毁了。

 

POST Request

POST 和 GET 大同小异

POST JSON Data

const request = new XMLHttpRequest();
request.open('POST', 'https://192.168.1.152:44300/products');
request.setRequestHeader('Accept', 'application/json');
request.setRequestHeader('Content-Type', 'application/json');
request.responseType = 'json';
request.addEventListener('readystatechange', () => {
  if (request.readyState === 4) {
    console.log(request.status);   // 201
    console.log(request.response); // { id: 1, name: 'iPhone12' }
  }
});
const product = { name: 'iPhone12' };
const json = JSON.stringify(product);
request.send(json);

JSON 格式需要添加 request header 'Content-Type',然后是 request.send 方法传入要 post 的资料就可以了。

POST FormData

POST FormData 和 POST JSON data 大同小异

const productFormData = new FormData();
productFormData.set('name', 'iPhone12');
request.send(productFormData);

只是把 send 的资料改成 FormData 就可以了。

另外,FormData 不需要设置 request header 'Content-Type',游览器会依据 send 的 data 类型自动添加,JSON 之所以需要是因为游览器把 JSON 视为 text/plain。

POST Binary (Blob)

FormData 支持 Blob 类型的 value,所以我们可以使用 FormData 上传二进制文件。

const productFormData = new FormData();
productFormData.set('name', 'iPhone12');

const productDocument = 'Product Detail';
const textEncoder = new TextEncoder();
const bytes = textEncoder.encode(productDocument);
const blob = new Blob([bytes], {
  type: 'text/plain',
});
productFormData.set('document', blob);
request.send(productFormData);

或者直接发送 Blob 也是可以的。

const productDocument = 'Product Detail';
const textEncoder = new TextEncoder();
const bytes = textEncoder.encode(productDocument);
const blob = new Blob([bytes], {
  type: 'text/plain',
});
request.send(blob);

如果二进制没有明确类型,type 就放 application/octet-stream。

upload progress

和 download 是同一个原理

request.addEventListener('progress', e => {
  const percentage = ((e.loaded / e.total) * 100).toFixed() + '%';
  console.log(percentage);
});

如果又有上传又有下载,可以通过 request.readystate 做区分

request.addEventListener('progress', e => { 
  if (request.readyState >= 3) { // 3 是 loading,4 是 done
    const downloadPercentage = ((e.loaded / e.total) * 100).toFixed() + '%';
  } else {
    const uploadingPercentage = ((e.loaded / e.total) * 100).toFixed() + '%';
  }
});

 

Request ReadyState

0 是初始阶段。

1 是设置了 request method 和 URL 之后

2.是 response header 下载完毕

3 是 downloading response body

4 是 response body 下载完毕

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章