侧边栏

微信小程序之请求拦截器

发布于 | 分类于 前端/小程序

初次接触到请求拦截器是在Vue-ReSource中,后来使用axiosrestify实现jwt单页面应用用户验证的时候也用到了路由拦截器的功能。

在微信小程序中,官方只提供了最基本的wx.request,现在打算对这个接口进行封装,在小程序中实现简单的路由拦截器。

我对于路由拦截器的理解是:在发送请求之前和**接收到响应后(处理数据之前)**执行的某些操作。整个过程可以看作:

发送请求前——发送请求——接收到响应后——处理响应数据

“发送请求”和“处理响应数据”在每个请求中可能都是不相同的,但是“ 发送请求前”和“接收到响应后”的逻辑处理可能都相似的,举个很常见的需求:发送请求时显示Loading提示,接受到响应后关闭提示。

鉴于每次请求都得实现这样的需求,因此必须采取某种措施统一处理,这里的措施指的就是拦截器

修饰函数

看见上面的执行顺序,直接想到的是使用回调函数修饰wx.request

javascript
Function.prototype.before = function(beforeFn){
    var _self = this;
    return function(){
        beforeFn.apply(this, arguments);
        _self.apply(this, arguments);
    }
}
Function.prototype.after = function(afterFn){
    var _self = this;
    return function(){
        _self.apply(this, arguments);
        afterFn.apply(this, arguments);
    }
}

var ajax = function(){
    console.log("send request");
}

var modifiyFn = ajax.before(function(){
    console.log("config params...");
}).after(function(){
    console.log("recive some data...");
});

modifiyFn();

上面的代码看起来能达到我们的需求,实现起来也很方便,但是我们忽略了一个很关键的问题:请求是异步的!wx.request的返回数据是通过制定配置参数的success方法处理的

wx.request({
  	url,
  	data,
  	success(res){
      	// res.data
  	}
})

Promise实现

很明显,通过为请求方法添加修饰函数并不能达到我们的需求,因为同步代码总是优于异步代码执行的。转念一想,链式的异步调用Promise来处理刚刚好啊。

接口promosie化

前面提到,小程序的请求采用的是指定回调函数的方式而没有进行Promise,不过微信的接口大都比较统一:

  • success指定成功处理函数
  • fail指定失败处理函数

这样我们可以自己将接口封装一下

javascript
let wxPromise = function (fn){
    return function (opt = {}) {
        return new Promise((resolve, reject) => {
            opt.success = function (res) {
                console.log(resolve);
                resolve(res);
            };

            opt.fail = function (res) {
                reject(res)
            };

            fn(opt);
        })
    }
};

然后将wx.request进行封装,需要注意这里可能会改变原生wx.request中的this指向,如果内部实现没有使用wx的话,这个问题应该也没啥吧

js
// 获取Promise风格的wx.request接口
let wxRequest = wxPromise(wx.request);
// 测试
wxRequest({ url, data }).then((res)=>{
  	console.log(res.data);
})

Promise的链式调用

OK,现在我们已经可以使用promise的风格来调用wx.request()了,整个拦截器的基本思路就是依次调用请求拦截器,具体请求和响应拦截器。

不论是拦截器还是具体请求,我们都将他们看作是某个抽象的操作,然后使用一个队列进行维护并依次调用,只要每个操作都返回了正确的值,Promise的链式操作就可以执行下去,之前实现了一个简单的Promise,关于Promisethen方法链式调用,这里就不折腾了。

这个队列应该保存着拦截操作和具体请求,具体请求在前面已经提到了,就是封装了得wx.request,那么拦截操作应该是什么样子呢?无非是回调函数罢了,只不过为了维持Promise的链式调用,每个操作都应该返回值。

js
// 执行队列
let chain = [(config)=>{
	return wxRequest(config).then((result)=>{
		return result;
	});
}, undefined];

// 拦截器大概就是这个样子的
let interceptors = {
  	request: [{
      	fulfilled(){},
      	rejected(){},
  	}],
  	response: [{
      	fulfilled(){},
      	rejected(){},
  	}]
};

// 添加请求拦截器到执行队列前方
interceptors.request.forEach((item)=>{
	chain.unshift(item.fulfilled, item.rejected);
});
// 添加响应拦截器到执行队列后方
interceptors.response.forEach((item)=>{
	chain.push(item.fulfilled, item.rejected);
});

// chain现在拥有了基本完整的执行链,除了处理响应数据的处理,这个是需要在最后的then方法中自己注册的

那么,如何将队列中保存这的拦截操作和具体请求(一般都是回调函数)转变成Promise对象呢呢?这个在之前写的一个”NodeJS创建层级目录“的插件(代码写的太烂现在废弃掉了)中尝试过,使用一个while循环就可以了,在axios中发现了更准确的写法

javascript
let chain = []; 

let promise = Promise.resolve(config);
while(chain.length){
	// 相邻元素一个为resolve,一个为reject
	promise = promise.then(chain.shift(), chain.shift());
}
// 最后拿promise搞点什么事情
return promise;

完整实现

上面并没有提到如何注册拦截器,实际上无非就是向request和response中添加回调函数罢了,下面上完(cou)整(zi)代(shu)码

javascript
import CONFIG from "./_config";
import { wxRequest } from "../utils/wxPromise";

class Resource{
    constructor(){
        this.interceptors = {
            request: [],
            response: []
        };

        this.config();
    }

    config(params = CONFIG){
        this.host = params.host ;

        return this;
    }

    before(opt){
        this.interceptors.request.unshift(opt);
        return this;
    }
    after(opt){
        this.interceptors.response.push(opt);
    }

    request(url, method ,params = {}){

        // 还原wx.request的参数格式
        let config = {
            url: this.host + url,
            data: params,
            method: method
        };

        let chain = [(config)=>{
            return wxRequest(config).then((result)=>{
                // 将响应状态和数据绑定到resource实例上
                this.status = result.statusCode;
                this.data = result.data;

                return result;
            });
        }, undefined];

        // 添加中间件到执行链上
        this.interceptors.request.forEach((item)=>{
            chain.unshift(item.fulfilled, item.rejected);
        });
        this.interceptors.response.forEach((item)=>{
            chain.push(item.fulfilled, item.rejected);
        });

        let promise = Promise.resolve(config);

        while(chain.length){
            promise = promise.then(chain.shift(), chain.shift());
        }

        return promise;
    }

}

// 基本方法
['GET', 'POST', 'HEAD', 'DELETE', 'PUT'].forEach((item)=>{
    let method = item.toLowerCase();
    Resource.prototype[method] = function (url, params = {}) {
        return this.request(url, item ,params);
    }
});

let resource = new Resource();

export default resource;

中间还做了一点其他处理,按照HTTP请求方式注册了快捷请求方法(主要是为了与后台实现RESTful接口)。实际上我认为拦截器的实现,最主要的就是理解Promise的链式调用,以及操作队列的正确顺序。

测试

下面来简单测试一下

javascript
resource.before({
    fulfilled(config){
        console.log("begin ...");
        wx.showLoading({
            title: '加载中',
        });
        return config;
    }
});

resource.after({
    fulfilled(data){
        wx.hideLoading();
        return data;
    }
});

Page({
   onLoad(){
       resource.post("test", {name:"txm"}).then((data)=>{
           console.log(data);
       })
   }
});

由于都是基于Promise的,因此也可以在拦截器中执行异步操作,并在操作完成之后再进行具体操作。想到之前那个在每个页面都发送重复的用户身份验证真是醉了,尽管不是我写的(/掩面)。

总之,这个拦截器只是在学习并实现Promise之后用来练手的一个小项目,本身跟小程序也没有特别大的关系,一切都是碰巧了,哈哈...

你要请我喝一杯奶茶?

版权声明:自由转载-非商用-保持署名和原文链接。

本站文章均为本人原创,参考文章我都会在文中进行声明,也请您转载时附上署名。