微信小程序之请求拦截器
初次接触到请求拦截器是在Vue-ReSource
中,后来使用axios
和restify
实现jwt
单页面应用用户验证的时候也用到了路由拦截器的功能。
在微信小程序中,官方只提供了最基本的wx.request
,现在打算对这个接口进行封装,在小程序中实现简单的路由拦截器。
我对于路由拦截器的理解是:在发送请求之前和**接收到响应后(处理数据之前)**执行的某些操作。整个过程可以看作:
发送请求前——发送请求——接收到响应后——处理响应数据
“发送请求”和“处理响应数据”在每个请求中可能都是不相同的,但是“ 发送请求前”和“接收到响应后”的逻辑处理可能都相似的,举个很常见的需求:发送请求时显示Loading提示,接受到响应后关闭提示。
鉴于每次请求都得实现这样的需求,因此必须采取某种措施统一处理,这里的措施指的就是拦截器。
修饰函数
看见上面的执行顺序,直接想到的是使用回调函数修饰wx.request
。
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
指定失败处理函数
这样我们可以自己将接口封装一下
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
的话,这个问题应该也没啥吧
// 获取Promise风格的wx.request接口
let wxRequest = wxPromise(wx.request);
// 测试
wxRequest({ url, data }).then((res)=>{
console.log(res.data);
})
Promise的链式调用
OK,现在我们已经可以使用promise
的风格来调用wx.request()
了,整个拦截器的基本思路就是依次调用请求拦截器,具体请求和响应拦截器。
不论是拦截器还是具体请求,我们都将他们看作是某个抽象的操作,然后使用一个队列进行维护并依次调用,只要每个操作都返回了正确的值,Promise
的链式操作就可以执行下去,之前实现了一个简单的Promise,关于Promise
的then
方法链式调用,这里就不折腾了。
这个队列应该保存着拦截操作和具体请求,具体请求在前面已经提到了,就是封装了得wx.request
,那么拦截操作应该是什么样子呢?无非是回调函数罢了,只不过为了维持Promise
的链式调用,每个操作都应该返回值。
// 执行队列
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
中发现了更准确的写法
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)码
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
的链式调用,以及操作队列的正确顺序。
测试
下面来简单测试一下
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
之后用来练手的一个小项目,本身跟小程序也没有特别大的关系,一切都是碰巧了,哈哈...
你要请我喝一杯奶茶?
版权声明:自由转载-非商用-保持署名和原文链接。
本站文章均为本人原创,参考文章我都会在文中进行声明,也请您转载时附上署名。