Skip to content

08-从架构分析API层

统计信息:字数 6712 阅读14分钟

Axios 学习介绍

axios 如何实现多种请求方式(get-post)axios 如何请求拦截

Axios 源码分析

我们通常的思路是给axios的原型上绑定很多方法,绑定方法很多不好维护

function axios() {
  //
}
axios.prototype.post = function() {
  // 
}
axios.prototype.get = function() {
  //
}

官方的源码如下:

首先加入两个辅助函数

function bind(fn, thisArg) {
  return function wrap() {
    var args = new Array(arguments.length);
    for (var i = 0; i < args.length; i++) {
      args[i] = arguments[i];
    }
    return fn.apply(thisArg, args);
  };
};

function extend(a, b, thisArg) {
  forEach(b, function assignValue(val, key) {
    if (thisArg && typeof val === 'function') {
      a[key] = bind(val, thisArg);
    } else {
      a[key] = val;
    }
  });
  return a;
}

extend(
    {a: 1, fn: function(){}},
  {b: 2},
  this
);

// 初始化方式:最终暴露的是一个非常简单的类(类似于VUE)
// 然后模块合并(不同人负责不同的部分)

function axios(instanceofConfig) {
  this.default = instanceofConfig;
}

axios.prototype.request = function() {

}

function createInstance(defaultConfig) {
  let context = new axios(defaultConfig);
  let instance = bind(Axios.prototype.request, context);
  extend(instance, Axios.prototype, context);
  extend(instance, context);
  return instance;
}

utils.forEach(['get', 'post', 'delete', 'head', 'options'], function(methods) {
  Axios.prototype[methods] = function(url, config) {
    return this.request(utils.merge());
  }
})

VUE对外也是暴露一个非常简单的类

function Vue(options) {
  if (!(this instanceof Vue)) {
    warn('error');
  }
  this._init();
}

initMixin(Vue);
stateMixin(Vue);
eventsMixin(Vue);
lifestyleMixin(Vue);
renderMixin(Vue);

二次封装axios实例

myaxios.js

// 最终暴露出去的是一个很简单的类,其他功能通过模块合并方式处理
function axios(instanceofConfig) {
  this.default = instanceofConfig;
  this.interceptors = {
    request: new interceptorsManner(),
    response: new interceptorsManner(),
  };
}
axios.prototype.request = function() {
  //
}
function createInstance(defaultConfig) {
  var context = new axios(defaultConfig);
  var instance = bind(Axios.prototype.request, context);
  // Axios.prototype.request.bind(context);
  extend(instance, Axios.prototype, context);
  extend(instance, context);
  return instance;
}
axios.get(url);
axios.post(url, {
  data: 123
});

// Axios 中如何实现多种请求?
// 根据传入的参数判断。如果是get只有一个参数,如果是post后面就有具体的数据
utils.forEach(['get', 'post', 'delete', 'options'], function(methods) {
  Axios.prototype[methods] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: methods,
      url: url
    }));
  }
});

function interceptorsManner() {
  this.handler = []; // 存放use加入的方法
}
interceptorsManner.prototype.use = function(fulfilled, rejected) {
  this.handler.push({
    fulfilled: fulfilled,
    rejected: rejected,
  });
}

// axios 如何实现请求拦截
axios.interceptors.request.use(config => {
  // return config;
  // dispatchRequest 是默认的发送请求,因为需要双数,后面加入undefined
  var chain = [dispatchRequest, undefined];
  var promise = Promise.resolve(config);
  // 把请求和相应分别加入到数组序列(chain)的前后,依次执行
  this.interceptors.request.handler.forEach((interceptor) => {
    chain.unshift(interceptor.fulfilled, interceptor.injected);
  });
  this.interceptors.response.handler.forEach((interceptor) => {
    chain.push(interceptor.fulfilled, interceptor.injected);
  });
  // 把数组变成promise和then回调函数形式
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }
  return promise;
});

//就是下面的链式Promise形式
new Promise(function() {
  resolve();
  return 1;
}).then(function(res) {
  resolve();
  return 2
}).then(function(res) {
  resolve();
  return 3
});

axios.interceptors.response.use(config => {
  return config;
});

拦截的步骤
1初始化 axios
2调用 request 方法
3请求拦截器
4实际发出请求返回响应
5响应拦截器

这个课程需要很好的基础JS,看起来有点吃力(这才是提升)

大型项目api层

大型项目 API 复杂,专门设置一个api层

可以管理前后拦截,统一封装API

实现:接口API化,请求自动绑定,放置重复提交

login.js

export default {
  loginIn: '/api/loginIn',
  loginOut: '/api/loginOut',
};

server.js

import axios from 'axios';
const server = axios.create({
  baseURL: 'http://localhost:3000',
  timeout: 5000
});
// 设置拦截器
export default server;

axios.get('login/liginIn').then(function(res) {
  this.data.xxx = res.data;
});
qa.login.loginIn();

getrequest.js

import server from './server.js';
import qs from 'qs';
// qs({a: 1, b:2}) => "?a=1&b=2"

function myserver() {
  this.server = server;
}

myserver.prototype.parseRouter = function(name, urlOb) {
  var ob = this[name] = {};
  // var ob = {}, this[name] = {};
  Object.keys(urlOb).forEach((item) => {
    ob[item] = this.sendMes.bind(this, name, item, urlOb[item]);
  });
}

myserver.prototype.sendMes = function(moduleName, name, url, config) {
  var config = config || {};
  var type = config.type || 'get';
  var data = config.data || {};
  var self = this;

  var before = function(mes) {
    cover();
    send();
    return mes;
  };
  var defaultFn = function(mes) {
    self.nowhandle[bindName] = mes.data;
  }
  var success = config.success || defaultFn;
  var callback = function(res) {
    success(res, defaultFn);
  };

  var state = {
    get: function() {
      var urlqs = url + '?' + qs.stringify(data);
      server.get(urlqs).then(before).then(callback);
    },
    post: function() {
      server.post(url, data).then(before).then(callback);
    },
  };
  // 处理同一个接口的多个请求(只有状态是ready才能发出下一个相同的请求)
  // 这个很重要,避免用户频繁点击发送请求,或者恶意点击增加服务器负载
  if (self[moduleName][name].state == 'ready') {
    self[moduleName][name].state = 'pending';
    state[type]();
  }
}

export default new myserver;

main.js

import Vue from 'vue';
import App from './App';
import router from './router';
import qa from './api';
Vue.prototype.qa = qa;
Vue.config.productionTip = false;

new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
});

Last update: November 9, 2024