欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

异步请求和中断(XHR、Axios、Fetch Comparison)

最编程 2024-06-28 07:57:53
...

随着AJAX技术的诞生,前端正式进入了局部刷新和前后端分离的新时代,最初的服务请求技术是XHR,再到后来前端技术发展和ES6的诞生,jquery ajax,axios,fetch 等技术的产生让前端的异步请求更便捷.

当我们使用异步请求的时候可能会有中断请求的需要.

比如当我们第一次查询数据的时候没有输入查询条件导致查询很慢,于是我们第二次添加了查询调价重新查询很快结果返回并渲染到了页面,但是第一次的请求还在进行中,无法停止

当我们正在看数据的时候第一次的请求返回了结果并重新渲染了页面,导致数据混乱

各种请求技术怎么又该怎么实现呢?下边来分别进行简述:

一、XHR

1.说明

AJAX 使用的 XMLHttpRequest 的对象与服务器通信.让我们通过下面显示的图像了解 AJAX 的流程或 AJAX 的工作原理。

2.调用和中断

const xhr = new XMLHttpRequest();
const method = 'GET';
const url = 'https://xxx';
xhr.open(method, url, true);
xhr.onreadystatechange = () => {
  if (xhr.readyState === 4) {
    // do something
  }
}
xhr.send();

setTimeout(()=>{
    xhr.abort()}
,1000)   

jquery Ajax类似

var ajaxGet = $.get(
“https://xxx”, 
{id:1},  
function(data){
    …//一些操作 
});   
ajaxGet.abort(); 

二、axios

1.说明

众所周知xhr技术虽然实现了异步调用但是如果连续有序地调用多个请求就会出现回调地狱的尴尬场面.

ES6推出的async/await promise可以很好的解决这个问题.而axios就是基于promise对xhr进行的封装

核心代码如下(简单模拟非源码):

function axios(config){
        return new Promise((resolve) => {
            const {url='',data={},method='get'} = config; //解构传参
            const xhr = new XMLHttpRequest;     //创建请求对象
            xhr.open(method,url,true);
            xhr.onreadystatechange = () => {
                if(xhr.readyState == 4 && xhr.status == 200){
                    resolve(xhr.responseText);
                    //异步请求返回后将Promise转为成功态并将结果导出
                }
            }
            xhr.onerror = (err) => {
                reject(err);
            };
            xhr.send(JSON.stringfy(data));
        })
    }
    

2.使用

     // then catch 链式调用
       axios.get('/user')
       .then(function (response) {
         console.log(response);
         axios.get('class?info=' + response.data.name);
        })
       .catch(function (error) {
          console.log(error);
        });

     // async await 
       var info = await axios.get('user');
       var ret =  await axios.get('class?info=' + info.data.name);

3.中断(取消)

1. AbortController

v0.22.0 开始,Axios 支持以 fetch API 方式—— AbortController 取消请求:

const controller = new AbortController();

axios.get('/foo/bar', {
   signal: controller.signal
}).then(function(response) {
   //...
});
// 取消请求
controller.abort()

2. CancelToken(已废弃)

axios v0.22.0 以前 的中断取消是基于 cancelable promises proposal

原理是内部生成一个Promise 将 resove 方法抛给外部的 source的cancel方法,

当外部调用这个方法时,内部的promise.then就会调用xhr.abort() 并调用外部的reject

可以使用 CancelToken.source 工厂方法创建 cancel token,像这样:

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/user/12345', {
  cancelToken: source.token
}).catch(function(thrown) {
  if (axios.isCancel(thrown)) { // 取消处理
    console.log('Request canceled', thrown.message);
  } else {
     // 处理错误
  }
});
axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
})
// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.')

还可以通过传递一个 executor 函数到 CancelToken 的构造函数来创建 cancel token:

const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // executor 函数接收一个 cancel 函数作为参数
    cancel = c;
  })
});

// cancel the request
cancel();

三、Fetch

1.说明

Fetch也是基于ES6 Promise 实现的一个服务器请求技术,但不是对xhr的封装.

也是底层的实现不需要引入包,是 XMLHttpRequest 的升级版.兼容除了IE的大部分浏览器

缺点

  • fetch只对网络请求报错,对400,500都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。
  • fetch默认不会带cookie,需要添加配置项: fetch(url, {credentials: 'include'})
  • fetch不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费
  • fetch没有办法原生监测请求的进度,而XHR可以

2.基本使用

     // then  catch 链式调用
     fetch('https://xxxx')
     .then(response => response.json())
     .then(json => console.log(json))
     .catch(err => console.log('Request Failed', err)); 
     // async await
     async function getJSON() {
         let url = 'https:XXXX';
         try {   
             let response = await fetch(url);
             return await response.json();  
          } catch (error) { 
             console.log('Request Failed', error); 
          }
     }

3.中断

Fetch的中断是基于webApi的 AbortController(实验阶段的功能兼容除了IE的大部分浏览器)

var controller = new AbortController();
var signal = controller.signal;

// 可以监听取消事件
signal.addEventListener('abort',
  () => console.log('abort!')
);

setTimeout(()=>{
    //定时或者手动调用abort方法中断
     controller.abort();
  },1000)

 fetch('http://xxxx', {signal}).then(function(response) {
    ...
  }).catch(function(e) {
    if(signal.aborted){
     // 可以通过 signal.aborted 属性判断
      ...
    }
    if(e.name=='AbortError'){
     // 也可以通过 error.name 判断
     ...
    }
  })

4. 缺点

  1. fetch只对网络请求报错,对400,500都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。
  2. fetch默认不会带cookie,需要添加配置项: fetch(url, {credentials: 'include'})
  3. fetch不支持超时控制,xhr支持
xhr.timeout = 10000; 
axios({  method: 'get',  url: '  timeout: 5000 })
  1. fetch没有办法原生监测请求的进度,而XHR可以
// xhr 给我们提供了一个 progress 事件,这里的 progress 事件只监听响应。
// 每当服务器传输完一小段数据之后就会触发 progress 事件
xhr.addEventListener("progress", (e) => {
      // e.loaded 当前加载量   e.total 总量
      console.log(e.loaded, e.total);
});
axios({url,onDownloadProgress(progress){}})
//fetch 实现 文件下载进度监测
/* https://javascript.info/fetch-progress */
;(async () => {
    // Step 1: start the fetch and obtain a reader
    let response = await fetch('xxx');
    const reader = response.body.getReader();
    // Step 2: get total length
    const contentLength = +response.headers.get('Content-Length');
    // Step 3: read the data
    let receivedLength = 0; // received that many bytes at the moment
    let chunks = []; // array of received binary chunks (comprises the body)
    while (true) {
        const { done, value } = await reader.read();
        if (done) {
            break;
        }
        chunks.push(value);
        receivedLength += value.length;
        console.log(`Received ${receivedLength} of ${contentLength}`, )
        const percent = Math.round(receivedLength / contentLength * 100)
        console.log(percent + '%')
    }
    // Step 4: concatenate chunks into single Uint8Array
    let chunksAll = new Uint8Array(receivedLength); // (4.1)
    let position = 0;
    for (let chunk of chunks) {
        chunksAll.set(chunk, position); // (4.2)
        position += chunk.length;
    }
    // Step 5: decode into a string
    let result = new TextDecoder("utf-8").decode(chunksAll);
    // We're done!
    console.log( result)
})();

四 、阻止渲染通用实现方法

其实在知道这些中断方法之前,我还用过其他的方法——uuid

主要思路就是每次调用请求的时候生成一个uuid,将这个uuid赋值给全局的变量同时作为参数传给请求的方法.

在请求返回处理数据的时候验证当前的全局uuid 是否和当前调用参数是否一致,不一致就不渲染数据,

这样就能保证渲染的数据是最后一次调用请求的数据

//以Fetch为例

this.uuid = ""

 // 自己写一个生成uuid的方法,或者使用第三方的包
function genUUID(){
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = (Math.random() * 16) | 0
        var v = c === 'x' ? r : (r & 0x3) | 0x8
        return v.toString(16)
    })
}

function fetchData(){
  // 赋值给局部变量和全局变量
  let  uuid = genUUID()
  this.uuid = uuid
  fetch(url).then(res=>{
     if(this.uuid === uuid){
      // 渲染数据
     }
   })

}

参考链接:
www.cnblogs.com/ITCoNan/p/1…
www.w3cschool.cn/ajax/ajax-t…
www.cnblogs.com/ysk123/p/11…