JavaScript 中断请求几种方案详解

2022-04-15 0 1,128
目录
  • 1 Promise
    • 中断调用链
    • 中断Promise
    • 包装abort方法——仿照Axios的CancelToken
  • 2 RXJS的unsubscribe方法
    • 3 Axios的CancelToken

      1 Promise

      Promise有一个缺点是一旦创建无法取消,所以本质上Promise是无法被终止的.

      但是我们可以通过中断调用链或中断Promise来模拟请求的中断.

      中断调用链

      中断调用链就是在某一个then/catch执行之后,后续的链式调用(包括then,catch,finally)不再继续执行.

      方法是在then/catch返回一个新的Promise实例,并保持pending状态:

      new Promise((resolve, reject) => {
          setTimeout(() => {
              resolve('result');
          });
      }).then(res => {
          // 达到某种条件,return一个pending状态的Promise实例,以中断调用链
          if (res === 'result') {
              return new Promise(() => {});
          }
          console.log(res); // 不打印
      }).then(() => {
          console.log('then不执行'); // 不打印
      }).catch(() => {
          console.log('catch不执行'); // 不打印
      }).finally(() => {
          console.log('finally不执行'); // 不打印
      });
      

      中断Promise

      中断Promise不等同于中止Promise,因为Promise是无法被终止的.

      这里的中断指的是,在合适的时机,把pending状态的promise给reject掉.例如一个常见的应用场景就是给网络请求设置超时时间,一旦超时就中断.

      老规矩,用setTimeout来模拟网络请求.阀值设置为Math.random() * 3000表示随机3秒之内返回结果.

      const request = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve('收到服务端数据')
        }, Math.random() * 3000)
      })
      

      假设超过2秒就是网络超时,我们可以封装一个超时处理函数.

      由于网络请求所需的事件是随机的,因此可以利用Promise.race方法,达到超时reject的目的.

      const timeoutReject = (p1, timeout = 2000) => {
          const p2 = new Promise((resolve, reject) => {
              setTimeout(() => {
                  reject('网络超时');
              }, timeout);
          });
          return Promise.race([p1, p2]);
      };
      
      timeoutReject(request).then(res => {
          console.log(res);
      }).catch(err => {
          console.log(err);
      });
      

      包装abort方法——仿照Axios的CancelToken

      上面实现的方式并不灵活,因为中断Promise的方式有很多,不单单是网络超时.

      我们可以仿照Axios中CancelToken的核心源码,简单包装一个abort方法,供使用者随时调用.

      function abortWrapper(p1) {
          let abort;
          const p2 = new Promise((resolve, reject) => {
              abort = reject;
          });
          // 如果没有resolve或reject,p2的状态永远是pending
          const p = Promise.race([p1, p2]);
          p.abort = abort;
          return p;
      }
      
      const req = abortWrapper(request);
      req.then(res => {
          console.log(res);
      }).catch(err => {
          console.log(err);
      });
      
      setTimeout(() => {
          // 手动调用req.abort,将p2的状态改变为rejected
          req.abort('手动中断请求');
      }, 2000);
      

      如此封装的主要目的就是为了能够在Promise外部控制其resolve或reject,让使用者可以随时手动调用resolve(触发.then)或reject(触发.catch).

      需要注意的是,虽然Promise请求被中断了,但是promise并没有终止,网络请求依然可能返回,只不过那时我们已经不关心请求结果了.

      2 RXJS的unsubscribe方法

      rxjs本身提供了取消订阅的方法,即unsubscribe.

      let stream1$ = new Observable(observer => {
          let timeout = setTimeout(() => {
              observer.next('observable timeout');
          }, 2000);
      
          return () => {
              clearTimeout(timeout);
          }
      });
      let disposable = stream1$.subscribe(value => console.log(value));
      setTimeout(() => {
          disposable.unsubscribe();
      }, 1000);
      

      3 Axios的CancelToken

      Axios的CancelToken有两种使用方法:

      • 方法一
      import axios from 'axios';
      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 {
          // handle error
        } 
      });
      
      source.cancel('Operation canceled by the user.');
      
      • 方法二
      import axios from 'axios';
      const CancelToken = axios.CancelToken;
      
      // 创建一个变量如 cancel 用于存储这个中断某个请求的方法
      let cancel;
      
      axios.get('/user/12345', {
        cancelToken: new CancelToken(function executor(c) {
          cancel = c; // 将参数 c 赋值给 cancel
        })
      });
      
      // 判断 cancel 是否为函数,确保 axios 已实例化一个CancelToken
      if (typeof cancel === 'function') {
          cancel();
          cancel = null;
      }
      

      CancelToken的核心源码:(axios/lib/cancel/CancelToken.js)

      'use strict';
      
      var Cancel = require('./Cancel');
      
      /**
       * A `CancelToken` is an object that can be used to request cancellation of an operation.
       *
       * @class
       * @param {Function} executor The executor function.
       */
      function CancelToken(executor) {
        if (typeof executor !== 'function') {
          throw new TypeError('executor must be a function.');
        }
      
        var resolvePromise;
        this.promise = new Promise(function promiseExecutor(resolve) {
          resolvePromise = resolve;
        });
      
        var token = this;
        executor(function cancel(message) {
          if (token.reason) {
            // Cancellation has already been requested
            return;
          }
      
          token.reason = new Cancel(message);
          resolvePromise(token.reason);
        });
      }
      
      /**
       * Throws a `Cancel` if cancellation has been requested.
       */
      CancelToken.prototype.throwIfRequested = function throwIfRequested() {
        if (this.reason) {
          throw this.reason;
        }
      };
      
      /**
       * Returns an object that contains a new `CancelToken` and a function that, when called,
       * cancels the `CancelToken`.
       */
      CancelToken.source = function source() {
        var cancel;
        var token = new CancelToken(function executor(c) {
          cancel = c;
        });
        return {
          token: token,
          cancel: cancel
        };
      };
      
      module.exports = CancelToken;
      

      可以看到,在Axios底层,CancelToken的核心源码所体现的思想,与上面中断Promise包装abort方法的思想一致.

      只不过Axios在外部手动调用resolve(用户触发cancel方法),而resolve一旦调用,就会触发promise的then方法,来看这个promise.then的源码:(axios/lib/adapters/xhr.js)

      if (config.cancelToken) {
        // Handle cancellation
        config.cancelToken.promise.then(function onCanceled(cancel) {
          if (!request) {
            return;
          }
      
          request.abort();
          reject(cancel);
          // Clean up request
          request = null;
        });
      }
      

      可以看到then方法中会执行abort方法取消请求,同时调用reject让外层的promise失败.

      到此这篇关于JavaScript 中断请求几种方案详解的文章就介绍到这了,更多相关js中断请求内容请搜索NICE源码以前的文章或继续浏览下面的相关文章希望大家以后多多支持NICE源码!

      免责声明:
      1、本网站所有发布的源码、软件和资料均为收集各大资源网站整理而来;仅限用于学习和研究目的,您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。 不得使用于非法商业用途,不得违反国家法律。否则后果自负!

      2、本站信息来自网络,版权争议与本站无关。一切关于该资源商业行为与www.niceym.com无关。
      如果您喜欢该程序,请支持正版源码、软件,购买注册,得到更好的正版服务。
      如有侵犯你版权的,请邮件与我们联系处理(邮箱:skknet@qq.com),本站将立即改正。

      NICE源码网 JavaScript JavaScript 中断请求几种方案详解 https://www.niceym.com/24069.html