Debounce节流

92 阅读4分钟

背景

当今时代,因为设备性能,网络延迟等问题,导致用户体验上哪怕卡顿了一秒,都会让用户体验感极差,甚至直接关闭。也会在点击或其他场景中,如卡顿延迟,用户发泄式高强度连续点击,这会导致短时间内持续发送服务器同样的htpp请求。当项目中完全相同的请求,反复多次进行无意义触发,既影响性能,又会导致资源浪费,给以服务器压力,甚至会让页面崩溃,陷入僵局。我们作为开发者,既要保证代码的功能与性能,又要保证代码健壮性与用户体验感。

思路

1.可以加loading,但是我们能不能再让用户体验流畅一点高级一点。

2.定时器做调http接口方法防抖,存在一个问题,无法解决不同的按钮或其他组件传参相同并且反复多次请求同样的接口,并且要重复写setTimeOut,我们能不能再优雅一点。

3.是否可以通过axios的CancelToken对象实例cancel方法把请求给取消掉,进而封装axios拦截器,完成对全局接口的防抖。

使用

image.png

先来看一下基础用法:

var CancelToken = axios. CancelToken;

var source = CancelToken. source();

axios

. get( '/user', {

cancelToken : source. token //get请求在第二个参数,post请求在第三个参数

})

. catch( function( thrown) {

});

source. cancel( '取消请求'); //token插入,cancel执行

我们可以发现,它要先引用axios.CancelToken,然后调用source()方法,会产生一个token和cancel,它的内部到底如何实现,这样做的目的是什么?

CancelToken中cancel原理

看看cancelToken的源码:

'use strict';

var Cancel = require( './Cancel');

  


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);

    });

}

  


CancelToken. prototype. throwIfRequested = function throwIfRequested() {

    if ( this. reason) {

        throw this. reason;

    }

};

  


CancelToken. source = function source() {

    var cancel;

    var token = new CancelToken( function executor( c) {

        cancel = c;

    });

    return {

        token : token,

        cancel : cancel

    };

};

module. exports = CancelToken;

CancelToken这个类初始化的时候需要传递一个方法executor,并且它的内部新建了一个promise,最关键的是,它把promise的resolve方法控制权放在了executor方法里面。

再看CancelToken的 source这个方法,我们可以看到,它new了一个CancelToken对象,并传了一个方法executor;采用相同的手法,用cancel变量将executor方法的变量c的控制权拿出来了,那么这个变量c又代表啥呢?

变量c正是我们前面说到的在CancelToken初始化时,传入executor方法的,也即:

function cancel( message) {
    if ( token. reason) {
      // Cancellation has already been requested
        return;
   }
    token. reason = new Cancel( message);
    resolvePromise( token. reason);
}

也就是说cancel代表的是上面的这个方法,有了这个方法,就可以在外部控制CancelToken内部的promise对象了。

在source方法中,除了cancel,还有一个token,这个token是CancelToken类的一个实例,可以访问到内部的promise。

因此CancelToken类如此封装的主要目的就是为了能够分离promise和resolve方法,让用户可以自己调用resolve方法。一旦resolve后,就会触发promise的then方法,现在看看内部promise后的then方法是什么:

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

axios/lib/adapters/xhr.js 文件中当用户调用cancel后,就会立即执行abort方法取消请求,同时调用reject让外层的promise失败。( abort为ajax取消方法)

实现

请求拦截器


                //请求拦截器

axios. interceptors. request. use(

    function ( config) {

    //根据请求的信息(请求方式,url,请求get/post数据),产生map的key

    let requestKey = getRequestKey( config)

    //判断请求是否重复

    if( pendingRequest. has( requestKey)){

        //取消上次请求

        let cancel = pendingRequest. get( requestKey)

        cancel()

        //删除请求信息

        pendingRequest. delete( requestKey)

    }

    //把请求信息,添加请求到map当中

    // 生成取消方法

    config. cancelToken = config. cancelToken || new axios. CancelToken( cancel => {

        // 把取消方法添加到map

        if (! pendingRequest. has( requestKey)) {

            pendingRequest. set( requestKey, cancel)

        }

    })

    return config;

  },

  ( error) => {

      return Promise. reject( error);

  }

);

响应拦截器


//删除请求信息

                function delPendingRequest( config){

                    let requestKey = getRequestKey( config)

                    if( pendingRequest. has( requestKey)){

                        pendingRequest. delete( requestKey)  

                    }

                }

                //响应拦截器

                axios. interceptors. response. use(

                ( response) => {

                        //请求成功

                        //删除请求的信息

                        delPendingRequest( response. config)

                        return response;

                },

                ( error) => {

                        //请求失败

                        //不是取消请求的错误

                        if (! axios. isCancel( error)){

                            //服务器报400,500报错,删除请求信息

                            delPendingRequest( error. config || {})

                        }

                        return Promise. reject( error);

                }

                );