记让一个http请求支持拦截器

45次阅读

共计 2755 个字符,预计需要花费 7 分钟才能阅读完成。

最近想用全 js 系统的写一遍前后端学习一下,就创建了一套 TODOList 项目练手。当前仅写完了后端 demo,前端正在使用 vue。并且准备以后再用 react 和 flutter 再写一遍。
此项目
后端 demo

前提

在写练手项目的时候使用了 Framework7 这个移动端 ui 框架,因为这个框架的动画写的很厉害所以选择了它。但是在使用过程中,发现这个框架自带的 ajax 请求库特别简单,于是参照 axios 手动封装了一下,使其支持 promise 和拦截器。

动手

废话不多说,上代码
comover.js

import {Request as F7Request} from "framework7";

// 将原始请求对象封装成 Promise 对象
function adapter(config) {return new Promise(function(resolve, reject) {
        F7Request({url: `${config.baseUrl}${config.url}`,
            method: config.method,
            headers: config.headers,
            data: config.data,
            success(data, status, xhr) {
                resolve({data: JSON.parse(data),
                    status: status,
                    config: config,
                    xhr: xhr
                });
            },
            error(xhr, status) {
                let error = new Error(`Request failed with status code ${status}`
                );
                error.xhr = xhr;
                reject(error);
            }
        });
    });
}

// 发送请求
function dispatchRequest(config) {return adapter(config).then(function onAdapterResolution(response) {return response;},
        function onAdapterRejection(reason) {return Promise.reject(reason);
        }
    );
}

export default class Comeover {interceptors = {};
    requestHandlers = [];
    responseHandlers = [];
    config = {};
    constructor(config = ({ baseUrl = ""} = {})) {
        const self = this;

        this.config = {...config};

        this.interceptors = {
            request: {use(fulfilled, rejected) {
                    self.requestHandlers.push({
                        fulfilled,
                        rejected
                    });
                }
            },
            response: {use(fulfilled, rejected) {
                    self.responseHandlers.push({
                        fulfilled,
                        rejected
                    });
                }
            }
        };
        
        // ES6 中 class 内方法运行时绑定上下文
        this.request = this.request.bind(this);
    }
    request(config) {
        // 合并默认 config 和发送请求时的 config
        let inconfig = {...this.config, ...config};
        
        // 创建 Promise 链
        let chain = [dispatchRequest, undefined];
        // 创建初始 Promise 链中传递的 promise 对象
        let promise = Promise.resolve(inconfig);
        
        // 将拦截器注入 Promise 链
        this.requestHandlers.forEach(interceptor => {chain.unshift(interceptor.fulfilled, interceptor.rejected);
        });
        this.responseHandlers.forEach(interceptor => {chain.push(interceptor.fulfilled, interceptor.rejected);
        });
        
        // 运行 Promise 链
        while (chain.length) {promise = promise.then(chain.shift(), chain.shift());
        }
        
        // 返回最终的 promise 对象
        return promise;
    }
}

使用

这个例子就是在所有请求前后使用 nprogress 假装显示一下请求进度

import Comeover from "./comeover";
import Np from "nprogress";

const baseUrl = process.env.NODE_ENV === "development" ? "": /* 上线地址 */"";

const comeover = new Comeover({baseUrl});

comeover.interceptors.request.use(
    config => {Np.start();
        return config;
    },
    error => {Np.done();
        return Promise.reject(error);
    }
);
comeover.interceptors.response.use(
    response => {Np.done();
        return response;
    },
    error => {Np.done();
        return Promise.reject(error);
    }
);

export {request};

发请求

comeover.request({
    url: "/api/login",
    method: "post",
    data: {
        email: this.email,
        password: this.password
    }
})
    .then(({data}) => {this.$store.commit("login", { token: data.message});
        router.back();})
    .catch(err => {app.dialog.alert("用户名或密码错误", "登陆失败");
    });

总结

还可以参照 axios 继续封装单独的 get、post 等等的方法,这个 demo 就不写了。
Promise 链是个数组,然后把请求拦截器放到真正请求的前面,响应后的拦截器放在真请求的后面。然后以 resolve 在前,reject在后的顺序,成对循环注入到 promise.then 中。而真正请求的 resovereject是写在 dispatchRequest 里的,所以 dispatchRequest 这里没有 reject,要加一个undefined
ES6 的实例化方法单独使用的时候 this 指向会有问题,需要单独处理

正文完
 0