那些浏览器原生网络请求方法

20 年 10 月 6 日 星期二 (已编辑)
2071 字
11 分钟

1. XMLHttpRequest (XHR)

XMLHttpRequest 是最早的异步网络请求 API,为现代 Web 应用奠定了基础。它提供了丰富的功能和细粒度的控制。

主要特点

  1. 支持同步和异步请求
  2. 可以设置请求头和获取响应头
  3. 支持各种数据类型(文本、JSON、XML等)
  4. 可以监控上传和下载进度
  5. 能够中止进行中的请求

基本用法

javascript
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.onload = function () {
  if (xhr.status === 200) {
    console.log(xhr.responseText);
  } else {
    console.error('Request failed. Status:', xhr.status);
  }
};
xhr.onerror = function () {
  console.error('Network error occurred');
};
xhr.send();

同步请求(不推荐)

XHR 支持同步请求,但这种用法已被废弃,因为它会阻塞主线程:

javascript
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', false); // 同步请求
xhr.send();
if (xhr.status === 200) {
  console.log(xhr.responseText);
}

取消请求

XHR 提供了一个简单直接的方法来取消进行中的请求:xhr.abort()

js
let xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);

xhr.send();

// 稍后取消请求
setTimeout(() => {
  xhr.abort();
  console.log('请求已取消');
}, 1000);

跨域处理

withCredentials这是 XHR 对象的一个布尔属性,默认值为 false,用于指示是否应该使用 credentials(如 cookies、HTTP 认证及客户端 SSL 证书)进行跨站点访问控制(CORS)请求。

js
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('GET', 'https://api.example.com/data', true);
xhr.send();
  1. 当设置为 true 时,浏览器会在跨域请求中包含 credentials, 它允许跨域请求携带敏感信息(如 cookies)。

  2. 只有在服务器端返回了 Access-Control-Allow-Credentials: true 头部时,浏览器才会将响应暴露给前端 JavaScript 代码。

  3. 当使用 withCredentials 时,Access-Control-Allow-Origin 不能设置为 *,必须指定具体的域名。

上传、下载进度

XHR 使用 progress 事件来跟踪下载进度,使用 upload.onprogress 事件来跟踪上传进度。

js
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://example.com/large-file');

// 下载进度
xhr.onprogress = function (event) {
  if (event.lengthComputable) {
    var percentComplete = (event.loaded / event.total) * 100;
    console.log('Download progress: ' + percentComplete.toFixed(2) + '%');
  }
};

xhr.onload = function () {
  console.log('Download completed');
};

xhr.send();
js
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://example.com/upload');

// 上传进度
xhr.upload.onprogress = function (event) {
  if (event.lengthComputable) {
    var percentComplete = (event.loaded / event.total) * 100;
    console.log('Upload progress: ' + percentComplete.toFixed(2) + '%');
  }
};

xhr.onload = function () {
  console.log('Upload completed');
};

var formData = new FormData();
formData.append('file', fileInput.files[0]);
xhr.send(formData);

2. Fetch API

Fetch API 是 XHR 的现代替代品,提供了更简洁、更强大的 API。它基于 Promise 设计,使异步操作更易于管理。

主要特点

  1. 基于 Promise,支持链式调用和 async/await
  2. 简洁的 API 设计
  3. 支持请求和响应流
  4. 内置 CORS 支持
  5. 更直观的请求配置

基本用法

javascript
fetch('https://api.example.com/data')
  .then((response) => {
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return response.json();
  })
  .then((data) => console.log(data))
  .catch((error) => console.error('Fetch error:', error));

取消请求

Fetch API 使用 AbortController 接口来取消请求。这个方法稍微复杂一些,但提供了更多的灵活性。

javascript
// 创建一个 AbortController 实例
const controller = new AbortController();

fetch('https://api.example.com/data', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ key: 'value' }),
  signal: controller.signal, // 这里把abort signal传入
})
  .then((response) => response.json())
  .then((data) => console.log(data));

// 1s后调用 controller.abort() 来取消请求
setTimeout(() => controller.abort(), 1000);

跨域处理

Fetch 默认遵循同源策略,但可以通过设置 modecredentials 选项来控制跨域行为:

javascript
fetch('https://api.example.com/data', {
  mode: 'cors',
  credentials: 'include',
})
  .then((response) => response.json())
  .then((data) => console.log(data));

credentials这里不再过多说明,请查看文档,而mode 选项则用于指定请求的模式,其中 corsno-cors 是两种常用的模式:

1. cors 模式

cors 是 Fetch 的默认模式,全称为"跨源资源共享"(Cross-Origin Resource Sharing),特点:

  1. 允许发送跨源请求
  2. 遵循 CORS 协议
  3. 可以访问响应的完整内容
  4. 需要服务器端的支持和配置

浏览器在发送实际请求前,可能会先发送一个预检请求(OPTIONS 方法),服务器需要设置适当的 CORS 头部来允许跨源访问,如果服务器配置正确,浏览器允许请求并可以访问响应。

2. no-cors 模式

no-cors 模式用于发送"简单"跨源请求,不触发 CORS 预检机制,特点:

  1. 可以发送跨源请求,但有严格限制
  2. 不需要服务器端的 CORS 支持
  3. 响应类型被限制为 "opaque",无法读取响应内容
  4. 主要用于缓存和某些资源(如图片)的加载

具体限制为:

  1. 只允许使用 GET、HEAD 或 POST 方法
  2. 不能设置自定义请求头
  3. 如果是 POST 请求,只能发送特定的 Content-Type(如 application/x-www-form-urlencoded、multipart/form-data 或 text/plain)

假设我们有一个场景,需要从第三方服务加载图片并缓存:

javascript
// 使用 no-cors 模式加载和缓存图片
fetch('https://third-party.com/image.jpg', {
  mode: 'no-cors',
})
  .then((response) => {
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return caches.open('image-cache').then((cache) => {
      return cache.put('https://third-party.com/image.jpg', response);
    });
  })
  .then(() => console.log('Image cached successfully'))
  .catch((error) => console.error('Caching failed:', error));

在这个例子中,我们使用 no-cors 模式来加载可能不支持 CORS 的第三方图片。虽然我们无法直接操作响应内容,但可以将其缓存起来供后续使用。

cors 和 no-cors 的主要区别整理:

  1. 访问响应内容
    • cors:可以完全访问响应内容。
    • no-cors:无法访问响应内容,响应类型为 "opaque"。
  2. 服务器配置要求
    • cors:需要服务器配置正确的 CORS 头部。
    • no-cors:不需要特殊的服务器配置。
  3. 请求方法和头部限制
    • cors:支持所有 HTTP 方法,可以设置自定义头部。
    • no-cors:仅限于 GET、HEAD 和 POST 方法,不能设置自定义头部。
  4. 用途
    • cors:用于需要读取响应内容的跨源 API 请求。
    • no-cors:主要用于加载跨源资源(如图片、脚本),或者向不支持 CORS 的 API 发送数据。
  5. 安全性
    • cors:提供更高的安全性,因为它需要服务器明确允许跨源访问。
    • no-cors:安全性较低,因为它允许发送跨源请求而不需要服务器的明确许可。
  6. 错误处理
    • cors:可以捕获和处理详细的错误信息。
    • no-cors:错误处理能力有限,因为无法访问响应详情。

上传、下载进度

Fetch API 本身不提供直接的进度事件,但可以通过 Response 对象的 body 属性(它是一个 ReadableStream)来实现类似的功能。

对于下载进度,可以使用 Response.bodyReadableStream API:

js
fetch('https://example.com/large-file')
  .then((response) => {
    const reader = response.body.getReader();
    const contentLength = +response.headers.get('Content-Length');
    let receivedLength = 0;

    return new ReadableStream({
      start(controller) {
        function push() {
          reader.read().then(({ done, value }) => {
            if (done) {
              controller.close();
              return;
            }
            receivedLength += value.length;
            const percentComplete = (receivedLength / contentLength) * 100;
            console.log('Download progress: ' + percentComplete.toFixed(2) + '%');
            controller.enqueue(value);
            push();
          });
        }
        push();
      },
    });
  })
  .then((stream) => new Response(stream))
  .then((response) => response.blob())
  .then((blob) => {
    console.log('Download completed');
  })
  .catch((error) => console.error('Error:', error));

对于上传进度,Fetch API 没有内置的方法,只能通过Blob StreamReadableStream模拟进度:

js
function uploadWithProgress(url, file) {
  const fileSize = file.size;
  let uploadedSize = 0;

  const stream = new ReadableStream({
    start(controller) {
      const reader = file.stream().getReader();

      function push() {
        reader.read().then(({ done, value }) => {
          if (done) {
            controller.close();
            return;
          }
          uploadedSize += value.byteLength;
          const progress = (uploadedSize / fileSize) * 100;
          console.log(`Upload progress: ${progress.toFixed(2)}%`);
          controller.enqueue(value);
          push();
        });
      }

      push();
    },
  });

  return fetch(url, {
    method: 'POST',
    body: stream,
  });
}

// 使用示例
const file = new File(['Hello, World!'], 'hello.txt', { type: 'text/plain' });
uploadWithProgress('https://example.com/upload', file)
  .then((response) => response.json())
  .then((data) => console.log('Upload complete:', data))
  .catch((error) => console.error('Upload failed:', error));

3. sendBeacon

sendBeacon 方法用于在页面卸载时发送少量数据,特别适合发送分析或日志数据, 可以在用户埋点时使用。

主要特点

  1. 异步发送数据,不阻塞页面卸载
  2. 数据发送优先级较高,提高了到达率
  3. 不需要等待服务器响应
  4. 可靠地发送少量数据

基本用法

javascript
window.addEventListener('unload', function () {
  navigator.sendBeacon(
    'https://analytics.example.com/log',
    JSON.stringify({
      event: 'page_exit',
      timestamp: new Date().getTime(),
    }),
  );
});

跨域处理

sendBeacon 不受同源策略限制,可以向任何域发送数据,这使得它特别适合发送分析或日志数据到第三方服务。

更多扩展

结合 Blob 使用以发送更复杂的数据:

javascript
class AnalyticsTracker {
  constructor(url) {
    this.url = url;
    this.data = [];
  }

  trackEvent(category, action, label) {
    this.data.push({ category, action, label, timestamp: new Date().toISOString() });
  }

  sendData() {
    const blob = new Blob([JSON.stringify(this.data)], { type: 'application/json' });
    navigator.sendBeacon(this.url, blob);
    this.data = []; // 清空数据
  }
}

const tracker = new AnalyticsTracker('https://analytics.example.com/log');

// 使用示例
tracker.trackEvent('button', 'click', 'submit-button');
window.addEventListener('unload', () => tracker.sendData());

tip:这个AnalyticsTracker只是个简单的示例,在实际应用中,AnalyticsTracker 可能会更复杂,包括更多功能,比如用户的跟踪、与特定分析平台(如 Google Analytics)的集成等。

SendBeacon的局限性

  1. 只支持 POST 请求
  2. 无法设置自定义请求头
  3. 不能接收响应数据
  4. 数据大小通常限制在 64KB 左右
  5. 不提供错误处理机制

文章标题:那些浏览器原生网络请求方法

文章作者:shirtiny

文章链接:https://kizamu.anror.com/posts/browser-network-fetch[复制]

最后修改时间:


商业转载请联系站长获得授权,非商业转载请注明本文出处及文章链接,您可以自由地在任何媒体以任何形式复制和分发作品,也可以修改和创作,但是分发衍生作品时必须采用相同的许可协议。
本文采用CC BY-NC-SA 4.0进行许可。