Cip5yGAY6X6ASgWfAAEY6D_f_OM734.png

功能设计与抽象粒度

合理的设计是 底层部分:保留对全局封装的影响范围 项目层:保留对页面层的影响能力 页面层:保留对组件层的影响能力

需要考虑的功能点:

  • 自定义 headers 添加
  • 统一断网/弱网处理
  • 接口缓存处理
  • 接口统一错误提示
  • 接口统一数据处理
  • 统一数据层结合
  • 统一请求埋点

    axios 设计思想

    功能特点:

  • 在浏览器端,使用 XMLHttpRequest 发送请求

  • 支持 Node.js 端发送请求
  • 支持 Promise API,使用 Promise 风格语法
  • 支持请求和响应拦截
  • 支持自定义修改请求和返回内容
  • 支持请求取消
  • 默认支持 XSRF 防御

    拦截器思想

    CgqCHmAY6WuAA9A_AAEfckV_ZjM495.png

    赋予了分层开发时借助拦截行为,注入自定义能力的功能

axios 拦截器由以下步骤组成:

  • 任务注册
  • 任务编排
  • 任务调度

    任务注册

    注入拦截逻辑,大多以数组的形式存储了使用方定义的一个个拦截器逻辑

  1. // lib/core/Axios.js
  2. function Axios(instanceConfig) {
  3. this.defaults = instanceConfig;
  4. this.interceptors = {
  5. request: new InterceptorManager(),
  6. response: new InterceptorManager()
  7. };
  8. }
  9. // lib/core/InterceptorManager.js
  10. function InterceptorManager() {
  11. // 存储所有的拦截器,但请求拦截器和响应拦截器是分开的
  12. this.handlers = [];
  13. }
  14. /**
  15. * 添加拦截器
  16. * fulfilled: 成功时执行的,在Promise.resolve中
  17. * rejected: 失败时执行的,在Promise.reject中
  18. *
  19. * 返回当前添加的拦截器的ID,用于清除这个拦截器
  20. */
  21. InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  22. // 把传入的在resolve和reject中要执行的方法添加到数组中
  23. this.handlers.push({
  24. fulfilled: fulfilled,
  25. rejected: rejected,
  26. });
  27. return this.handlers.length - 1;
  28. };
  29. /**
  30. * 根据id请求拦截器
  31. *
  32. * id: 刚才use方法返回的那个数据
  33. */
  34. InterceptorManager.prototype.eject = function eject(id) {
  35. if (this.handlers[id]) {
  36. this.handlers[id] = null;
  37. }
  38. };
  39. /**
  40. * 迭代所有的拦截器
  41. *
  42. * 这里会跳过之前使用eject方法设置为null的拦截器
  43. *
  44. * @param {Function} fn 对所有拦截器都执行的一个方法
  45. */
  46. InterceptorManager.prototype.forEach = function forEach(fn) {
  47. utils.forEach(this.handlers, function forEachHandler(h) {
  48. if (h !== null) {
  49. fn(h);
  50. }
  51. });
  52. };

任务编排

创建一个 chain 数组,把所有拦截器放进去,然后将请求拦截器插入到实际发送请求的方法前,响应拦截器插入该请求后

16051997763004.png

  1. // dispatchRequest 用于请求数据,这里我们先展示不管怎么实现的
  2. // 这里把 dispatchRequest 也当做拦截器添加到队列中
  3. // 每2个是一组,前面用于Promise.resolve, 后面的1个用户Promise.reject
  4. var chain = [dispatchRequest, undefined];
  5. // 拦截器调用forEach方法,把每一个请求拦截器都添加到chain的前面
  6. this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
  7. // 每2个是一组,前面用于Promise.resolve, 后面的1个用户Promise.reject
  8. // 由此也能看到,越是后添加的请求拦截器,越会是先执行
  9. chain.unshift(interceptor.fulfilled, interceptor.rejected);
  10. });
  11. // 拦截器调用forEach方法,把每一个响应拦截器都添加到chain的后面
  12. this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
  13. // 响应拦截器按照顺序执行
  14. chain.push(interceptor.fulfilled, interceptor.rejected);
  15. });

任务调度

通过一个 While 循环,通过一个 Promise 实例,遍历迭代 chain 数组方法,并基于 Promise 回调特性,将各个拦截器串联执行起来。

  1. // 把config初始化为一个Promise对象,方便后面的使用
  2. var promise = Promise.resolve(config);
  3. while (chain.length) {
  4. // 依次取出执行resolve和reject方法
  5. // 将执行后的结果传给下一个拦截器
  6. promise = promise.then(chain.shift(), chain.shift());
  7. }

适配器思想(Adapter)

安全思想

依赖双重 cookie 的方式防御 CSRF

  • 用户访问页面,后端向请求域中注入一个 cookie,一般该 cookie 值为加密随机字符串;
  • 在前端通过 Ajax 请求数据时,取出上述 cookie,添加到 URL 参数或者请求 header 中;
  • 后端接口验证请求中携带的 cookie 值是否合法,不合法(不一致),则拒绝请求。

    资料

  1. 学习 axios:封装一个结构清晰的 Fetch 库
  2. TypeScript 从零实现 axios
  3. axios 源码系列之拦截器的实现