编写一个axios这样的库

前言

本文需对jses6webpack网络请求等基础知识有基本了解php

相信axios你们都用过或者什么其余的网络请求相关的库,什么ajaxfly.js等等等等,光我用过的请求库就七八个,都大同小异html

本文并非要完彻底全一个字不差的实现axios全部功能,没有意义,可是也会实现的七七八八,主要是感觉一下这个流程和架子、以及 这个项目怎么才能易于拓展、是否是易于测试的、可读性怎么样等等等等webpack

废话很少说,开搞~ios

搭建项目

老规矩先建一个空目录,而后打开命令行执行c++

yarn init -ygit

es6

cnpm init -ygithub

webpack

而后是引入webpack,虽然本节不主讲webpack,这里我稍微提一嘴,webpackwebpack-cli不光项目里要下载,全局也要下载,也就是 yarn global add webpack webpack-cliweb

安装依赖包

执行命令,主要就须要这几个包,帮忙编译和调试的,babel帮助尽可能兼容浏览器的,毕竟我们写代码确定充满了es6ajax

yarn add webpack webpack-cli webpack-dev-server babel-loader @babel/core @babel/preset-env

配置webpack

接下来再在根目录建立webpack.config.js来配置一下webpack,而后再建一个src目录,来存放我们这个库的代码,如今的目录就会是这个样子

先简单配置一下,后续有需求在加,这里就直接上代码了

~ webpack.config.js

const path = require('path');

module.exports = function() {
  const dev = true;

  return {
    mode: dev ? 'development' : 'production',
    entry: './src/index.js',
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: dev ? 'axios.js' : 'axios.min.js',
      sourceMapFilename: dev ? 'axios.map' : 'axios.min.map',
      libraryTarget: 'umd',
    },
    devtool: 'source-map',
  };
};


复制代码

这时候在src里面,建一个index.js,而后随便写点东西,像这样

而后终端执行webpack命令

固然了,如今确定是不兼容的,要不我们一开始也不用下babel了,我们能够试试,好比我如今index.js加一句话

而后编译完能够看到结果也仍是let,这确定不行

好的,那么接下来就是配置babel,没什么可说的,这里直接放代码了,没什么可说的

const path = require('path');

module.exports = function() {
  const dev = true;

  return {
    mode: dev ? 'development' : 'production',
    entry: './src/index.js',
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: dev ? 'axios.js' : 'axios.min.js',
      sourceMapFilename: dev ? 'axios.map' : 'axios.min.map',
      libraryTarget: 'umd',
    },
    devtool: 'source-map',
    module: {
      rules: [
        {
          test: /\.js$/i,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env'],
            },
          },
        },
      ],
    },
  };
};

复制代码

而后,你们确定不但愿每次修改手动的去webpack一下对吧?把webpack-dev-server引进来

~ webpack.config.js

const path = require('path');

module.exports = function() {
  const dev = true;

  return {
    mode: dev ? 'development' : 'production',
    entry: './src/index.js',
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: dev ? 'axios.js' : 'axios.min.js',
      sourceMapFilename: dev ? 'axios.map' : 'axios.min.map',
      libraryTarget: 'umd',
    },
    devtool: 'source-map',
    module: {
      rules: [
        {
          test: /\.js$/i,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env'],
            },
          },
        },
      ],
    },
    devServer: {
      port: 8000,
      open: true,
    },
  };
};

复制代码

这时候,直接终端里运行webpack-dev-server的话其实他会自动去找全局的模块,这样很差,因此。。。你懂的

直接package.json里加上命令

~ package.json

{
  "name": "axios",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "webpack-dev-server"
  },
  "dependencies": {
    "@babel/core": "^7.7.7",
    "@babel/preset-env": "^7.7.7",
    "babel-loader": "^8.0.6",
    "webpack": "^4.41.5",
    "webpack-cli": "^3.3.10",
    "webpack-dev-server": "^3.10.1"
  }
}
复制代码

而后yarn start

这时候会弹出一个html

固然了,默认是去找根下的index.html,我们没有,因此在根下建一个,而后引入我们的axios.js

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>axios</title>
</head>
<body>
  <script src="/axios.js"></script>
</body>
</html>
复制代码

刷新页面就会看到src/index.js里的alert生效了

而且 webpack-dev-server也是能够的,改了代码页面会自动刷新

而后,我们就来配一下build

这里就很少废话了,直接上代码了

~ package.json

{
  "name": "axios",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "webpack-dev-server --env.dev",
    "build": "webpack --env.prod"
  },
  "dependencies": {
    "@babel/core": "^7.7.7",
    "@babel/preset-env": "^7.7.7",
    "babel-loader": "^8.0.6",
    "webpack": "^4.41.5",
    "webpack-cli": "^3.3.10",
    "webpack-dev-server": "^3.10.1"
  }
}

复制代码

~ webpack.config.json

const path = require('path');

module.exports = function(env={}) {
  const dev = env.dev;

  return {
    mode: dev ? 'development' : 'production',
    entry: './src/index.js',
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: dev ? 'axios.js' : 'axios.min.js',
      sourceMapFilename: dev ? 'axios.map' : 'axios.min.map',
      libraryTarget: 'umd',
    },
    devtool: 'source-map',
    module: {
      rules: [
        {
          test: /\.js$/i,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env'],
            },
          },
        },
      ],
    },
    devServer: {
      port: 8000,
      open: true,
    },
  };
};

复制代码

能够看到也是没问题的~

好的到这我本算是把webpack相关的东西搭的差很少了,接下来就要开始忙正事了~

Axios 项目代码

首先我们先来建一个common.js,用来放公共的方法,先写一个断言

~ /src/common.js

export function assert(exp, msg = 'assert faild') {
  if (!exp) {
    throw new Error(msg);
  }
}
复制代码

而后再建一个文件(我的习惯)/src/axios 主要的文件放在这

而后你们来看看axios正经怎么用,是否是能够直接 axios({...})或者axios.get等等

index.js引入直接写一下结果,把指望用法写上,而后来补充内部怎么写

~ index.js

import axios from './axios';

console.log(axios);
axios({ url: '1.txt', method: 'post' });
axios.get('1.txt', { headers: { aaa: 123 } });
axios.post(
  '1.txt',
  { data: 123 },
  {
    headers: {
      bbb: 123,
    },
  },
);

复制代码

这时候就要考虑考虑了,我们能够直接写函数,这个没问题,不过那样太散了,我的不喜欢,不过也是能够的,因此这里我就写成类了,因为改为类了那么输出出去的确定得是一个实例,既然是实例的话,那么确定也不能直接像函数同样直接()运行

没错,这时候就能够用到我们的proxy了,jsclass里的constructor里是能够return东西的,若是对这东西不太熟,建议先去看看jsclass,这里就很少赘述,主要说明思想

简单来讲,我们能够return一个proxy对象,来代理我们返回的结果,从而达到我们既能够直接用class的方式写,用的时候也能够直接跟函数同样()调用

而后先来打印一下看看

这时候看页面的console,这时候能够看到axios就是一个proxy对象,像这样

这时候还能看到一个报错,由于我们如今返回的是proxy对象,不是实例类了,没有get也是理所应当

可能有人会奇怪,为何这个proxy监听的对象非要单独监听一个proxy函数呢,直接监听this不就好了么,注意,这实际上是不行的,了解proxy的朋友应该知道,proxy你用什么监听建立的这事儿很重要,若是你监听的是一个对象,那仍是不能直接调用,若是要是想直接像函数同样直接调用的话,那你监听的也必须是一个函数

像这样

而后我们来解决一下get函数找不到的问题,来给proxy加一个方法, 很简单,能够给proxy加一个get方法,有人来找他,直接返回从我这个类上找,不就完了么,不过稍微要注意一下这个this,直接写this的话指向的是这个proxy

function request() {}

class Axios {
  constructor() {
    let _this = this;
    return new Proxy(request, {
      get(data, name) {
        return _this[name];
      },
      apply(fn, thisArg, args) {
        console.log(fn, thisArg, args);
      },
    });
  }

  get() {
    console.log('get');
  }

  post() {
    console.log('post');
  }

  delete() {}
}

let axios = new Axios();

export default axios;

复制代码

这时候再看,就没有报错了,而且getpost也能console出来

这时候我们就能够接着写数据请求了。。。。。吗? 远远不够

axios的参数有不少的多样性,我们想来大概总结一下

axios('1.txt',{})
axios({
    url: '1.txt',
    method
})

axios.get('1.txt')
axios.get('1.txt',{})

axios.post....

复制代码

等等吧,这一点,怎么才能把这么复杂多源的参数统一处理

第二就是axios能够很是深度的定制参数,能够全局也能够单独定制,拦截器,transfrom什么的,等等吧,默认值等等


参数

首先先来一个default,来定义一下这些默认值,这里稍微说一下这个X-Request-By算是一个不成文的规范,这也是广泛请求库都愿意作的事,方便后台去判断你这个请求是来自ajax仍是from仍是浏览器的url

function request() {}

const _default = {
  method: 'get',
  headers: {
    common: {
      'X-Request-By': 'XMLHttpRequest',
    },
    get: {},
    post: {},
    delete: {},
  },
};

class Axios {
  constructor() {
    let _this = this;
    return new Proxy(request, {
      get(data, name) {
        return _this[name];
      },
      apply(fn, thisArg, args) {
        console.log(fn, thisArg, args);
      },
    });
  }

  get() {
    console.log('get');
  }

  post() {
    console.log('post');
  }

  delete() {}
}

let axios = new Axios();

export default axios;

复制代码

固然了,这里图简单,简单写着几个参数,本身喜欢能够再加不少东西,好比data的默认值等等,先够用,后续不够用再加


这时候,我们来思考一下,这个default要加给谁,直接axios.default = _default么,固然不是,由于我们这个axios到最后确定是须要axios.create多个实例的,那么这时候就不行了,互相影响,prototype就更不用说了

其实也很简单,每次建立的时候从_default复制一份新的就能够了,直接JSON.parse(JSON.stringify(_default))包一下就能够了,这也是性能最高的方式,而后稍微来改造一下代码

function request() {}

const _default = {
  method: 'get',
  headers: {
    common: {
      'X-Request-By': 'XMLHttpRequest',
    },
    get: {},
    post: {},
    delete: {},
  },
};

class Axios {
  constructor() {
    let _this = this;
    return new Proxy(request, {
      get(data, name) {
        return _this[name];
      },
      apply(fn, thisArg, args) {
        console.log(fn, thisArg, args);
      },
    });
  }

  get() {
    console.log('get');
  }

  post() {
    console.log('post');
  }

  delete() {}
}

Axios.create = Axios.prototype.create = function() {
  let axios = new Axios();

  axios.default = JSON.parse(JSON.stringify(_default));

  return axios;
};

export default Axios.create();

复制代码

这里是给原型和实例都加了一个create方法,由于我们可能直接用axios.create()也可能直接用axios(),纯加静态方法或者实例方法都知足不了我们的需求

这时候咱们来实验一下,先来console一下axios.default

你会发现,undefined,这是为何呢,在这里明明都已经添加了呀

由于这时候这个axios并非一个对象,而是一个proxy,我们还没给proxyset方法对不对,加什么都加不上,这时候来改造一下代码

function request() {}

const _default = {
  method: 'get',
  baseUrl: "",
  headers: {
    common: {
      'X-Request-By': 'XMLHttpRequest',
    },
    get: {},
    post: {},
    delete: {},
  },
};

class Axios {
  constructor() {
    let _this = this;
    return new Proxy(request, {
      get(data, name) {
        return _this[name];
      },
      set(data, name, val) {
        _this[name] = val;

        return true;
      },
      apply(fn, thisArg, args) {
        console.log(fn, thisArg, args);
      },
    });
  }

  get() {
    console.log('get');
  }

  post() {
    console.log('post');
  }

  delete() {}
}

Axios.create = Axios.prototype.create = function() {
  let axios = new Axios();

  axios.default = JSON.parse(JSON.stringify(_default));

  return axios;
};

export default Axios.create();

复制代码

这时候再看浏览器,就会发现这个default就有了

以及我们来create两个axios,改一下参数试一下

两个实例参数也不影响,这也是很好的第n步,这时候我们就已经完成axios的四分之一了


我们如今实例间是不影响了,不过我们改改参数的时候毫不止直接axios.default.xxx这么改,我们还应该有参数,像这样

这里我们能够直接改造一下axios.create方法

~ axios.js

...
Axios.create = Axios.prototype.create = function(options={}) {
  let axios = new Axios();

  axios.default = {
    ...JSON.parse(JSON.stringify(_default)),
    ...options
  };

  return axios;
};

...
复制代码

直接展开替换一下就好了对不对,可是,真的么?

假设我们直接传了一个对象,里面有个headers的话是否是就直接给我们的默认参数header整个就得替换了呀,那这个就不太好,固然了,这个也看我们对本身这个库的需求,若是我们就想这么作,也到是没啥毛病,问题以下

那么这时候,我们就能够用一个小小的递归来搞定了

~ axios.js

...
Axios.create = Axios.prototype.create = function(options = {}) {
  let axios = new Axios();

  let res = { ...JSON.parse(JSON.stringify(_default)) };
  function merge(dest, src) {
      for (let name in src) {
        if (typeof src[name] == 'object') {
          if (!dest[name]) {
            dest[name] = {};
          }
    
          merge(dest[name], src[name]);
        } else {
          dest[name] = src[name];
        }
      }
    }

  merge(res, options);

  axios.default = res;
  return axios;
};
...
复制代码

这时候再看,就没问题了


代码整理拆分

接下来,我们先不急着去写请求的参数,我们先把这个代码稍微规划规划,整理整理,毕竟全放在一个文件里,这个之后就无法维护了

目前拆分能够分几点

  1. default 是否是能够用一个单独的文件来装
  2. 这个merge函数确定是公用的,能够放在我们的common.js
  3. 这个request也应该单独放在一个js里来定义

废话很少说,直接上代码

~ request.js

export default function request() {
  
}
复制代码

~ default.js

export default {
  method: 'get',
  baseUrl: '',
  headers: {
    common: {
      'X-Request-By': 'XMLHttpRequest',
    },
    get: {},
    post: {},
    delete: {},
  },
};
复制代码

~ common.js

export function assert(exp, msg = 'assert faild') {
  if (!exp) {
    throw new Error(msg);
  }
}

export function merge(dest, src) {
  for (let name in src) {
    if (typeof src[name] == 'object') {
      if (!dest[name]) {
        dest[name] = {};
      }

      merge(dest[name], src[name]);
    } else {
      dest[name] = src[name];
    }
  }
}
复制代码

~ axios.js

import _default from './default';
import { merge } from './common';
import request from './request';

class Axios {
  constructor() {
    let _this = this;
    return new Proxy(request, {
      get(data, name) {
        return _this[name];
      },
      set(data, name, val) {
        _this[name] = val;

        return true;
      },
      apply(fn, thisArg, args) {
        console.log(fn, thisArg, args);
      },
    });
  }

  get() {
    console.log('get');
  }

  post() {
    console.log('post');
  }

  delete() {}
}

Axios.create = Axios.prototype.create = function(options = {}) {
  let axios = new Axios();

  let res = { ...JSON.parse(JSON.stringify(_default)) };

  merge(res, options);

  axios.default = res;
  return axios;
};

export default Axios.create();

复制代码

这时候就感受干净很多了,对不对


处理请求参数

在写以前,我们先来看看axios都有哪些支持的写法,而后再去考虑怎么写

大概来看 除了那个总的axios({...})这三种方法是否是差很少呀,固然了,axios里东西太多了,这里就简单实现这三个,说明问题为主,你们有兴趣能够本身加,无非是体力活

固然,能够看到axios参数状况仍是蛮多的,这时候我们应该直接统一处理,无论传过来什么参数,我们都返回一个axios({})这种,最终统一处理,这不是方便么

这里直接来先判断一下前两种状况

你会发现前两种状况除了这个method都是同样的,那这个我们就能够抽出方法统一处理

~ axios.js

import _default from './default';
import { merge } from './common';
import request from './request';

class Axios {
  constructor() {
    let _this = this;
    return new Proxy(request, {
      get(data, name) {
        return _this[name];
      },
      set(data, name, val) {
        _this[name] = val;

        return true;
      },
      apply(fn, thisArg, args) {
        console.log(fn, thisArg, args);
      },
    });
  }

  _preprocessArgs(method, ...args) {
    let options;
    if (args.length == 1 && typeof args[0] == 'string') {
      options = { method, url: args[0] };
    } else if (args.length == 1 && args[0].constructor == Object) {
      options = {
        ...args[0],
        method,
      };
    } else {
      return undefined;
    }
    return options;
  }

  get(...args) {
    let options = this._preprocessArgs('get', args);

    if (!options) {
    }
  }

  post(...args) {
    let options = this._preprocessArgs('post', args);

    if (!options) {
    }
  }

  delete(...args) {
    let options = this._preprocessArgs('delete', args);

    if (!options) {
    }
  }
}

Axios.create = Axios.prototype.create = function(options = {}) {
  let axios = new Axios();

  let res = { ...JSON.parse(JSON.stringify(_default)) };

  merge(res, options);

  axios.default = res;
  return axios;
};

export default Axios.create();
复制代码

而后这时候,我们以封装一个库的视角,确定须要对参数进行各类各样的校验,类型等等,不对的话给他一个正经的报错,帮助使用者来调试

我们以前在common.js里写的assert这时候就用上了

...
get(...args) {
    let options = this._preprocessArgs('get', args);

    if (!options) {
      assert(typeof args[0] == 'string', 'args[0] must is string');
      assert(
        typeof args[1] == 'object' && args[1] && args[1].constructor == Object,
        'args[1] must is JSON',
      );

      // ...
    }
  }

  post(...args) {
    let options = this._preprocessArgs('post', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
      } else if (args.length == 3) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[1] == 'object' &&
            args[1] &&
            args[1].constructor == Object,
          'args[1] must is JSON',
        );
      } else {
        assert(false, 'invaild argments');
      }
    }
  }

  delete(...args) {
    let options = this._preprocessArgs('delete', args);

    if (!options) {
      assert(typeof args[0] == 'string', 'args[0] must is string');
      assert(
        typeof args[1] == 'object' && args[1] && args[1].constructor == Object,
        'args[1] must is JSON',
      );
      
    }
  }
}
...
复制代码

这里的规则对应着上面写的axios使用方式,大致来讲也差很少,把这些参数都校验好了,接下来,我们就能够写具体的个性化的处理了

顺便一说,这个地方固然也是能够重用的,不过不必,搞了一通其实也没减小多少代码,而且贼乱,也看我的,你们不喜欢能够本身修改

而后我们再来处理一下这个options,而且console一下

~ axios.js

import _default from './default';
import { merge, assert } from './common';
import request from './request';

class Axios {
  constructor() {
    let _this = this;
    return new Proxy(request, {
      get(data, name) {
        return _this[name];
      },
      set(data, name, val) {
        _this[name] = val;

        return true;
      },
      apply(fn, thisArg, args) {
        console.log(fn, thisArg, args);
      },
    });
  }

  _preprocessArgs(method, args) {
    let options;
    if (args.length == 1 && typeof args[0] == 'string') {
      options = { method, url: args[0] };
    } else if (args.length == 1 && args[0].constructor == Object) {
      options = {
        ...args[0],
        method,
      };
    } else {
      return undefined;
    }

    console.log(options);
    return options;
  }

  get(...args) {
    let options = this._preprocessArgs('get', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[1] == 'object' &&
            args[1] &&
            args[1].constructor == Object,
          'args[1] must is JSON',
        );

        options = {
          ...args[1],
          url: args[0],
          method: 'get',
        };
        console.log(options);
      } else {
        assert(false, 'invaild args');
      }
    }
  }

  post(...args) {
    let options = this._preprocessArgs('post', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        options = {
          url: args[0],
          data: args[1],
          method: 'post',
        };

        console.log(options);
      } else if (args.length == 3) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[2] == 'object' &&
            args[2] &&
            args[2].constructor == Object,
          'args[2] must is JSON',
        );
        options = {
          ...args[2],
          url: args[0],
          data: args[1],
          method: 'post',
        };
        console.log(options);
      } else {
        assert(false, 'invaild argments');
      }
    }
  }

  delete(...args) {
    let options = this._preprocessArgs('delete', args);

    if (!options) {
      assert(typeof args[0] == 'string', 'args[0] must is string');
      assert(
        typeof args[1] == 'object' && args[1] && args[1].constructor == Object,
        'args[1] must is JSON',
      );

      options = {
        ...args[1],
        url: args[0],
        method: 'get',
      };

      console.log(options);
    }
  }
}

Axios.create = Axios.prototype.create = function(options = {}) {
  let axios = new Axios();

  let res = { ...JSON.parse(JSON.stringify(_default)) };

  merge(res, options);

  axios.default = res;
  return axios;
};

export default Axios.create();

复制代码

这时候我们来测试一下

~ index.js

import Axios from './axios';

Axios.get('1.json');
Axios.get('1.json', { headers: { a: 12 } });

Axios.post('1.php');
Axios.post('1.php', { a: 12, b: 5 });
Axios.post('1.php', [12, 5, 6]);

let form = new FormData();
Axios.post('1.txt', form);
Axios.post('1.txt', 'dw1ewdq');

Axios.post('1.json', form, { headers: { a: 213, b: 132 } });

Axios.delete('1.json');
Axios.delete('1.json', { parmas: { id: 1 } });

复制代码

这时候能够看到,妥妥的对不对?

而后呢。。。还没忘,我们还须要处理直接apply的状况,也就是直接Axios()这么调用的时候

不废话,直接上代码,跟get其实差很少

~ axios.js

import _default from './default';
import { merge, assert } from './common';
import request from './request';

class Axios {
  constructor() {
    let _this = this;
    return new Proxy(request, {
      get(data, name) {
        return _this[name];
      },
      set(data, name, val) {
        _this[name] = val;

        return true;
      },
      apply(fn, thisArg, args) {
        let options = _this._preprocessArgs(undefined, args);
        if (!options) {
          if (args.length == 2) {
            assert(typeof args[0] == 'string', 'args[0] must is string');
            assert(
              typeof args[1] == 'object' &&
                args[1] &&
                args[1].constructor == Object,
              'args[1] must is JSON',
            );

            options = {
              ...args[1],
              url: args[0],
            };
            console.log(options);
          } else {
            assert(false, 'invaild args');
          }
        }
      },
    });
  }

  _preprocessArgs(method, args) {
    let options;
    if (args.length == 1 && typeof args[0] == 'string') {
      options = { method, url: args[0] };
    } else if (args.length == 1 && args[0].constructor == Object) {
      options = {
        ...args[0],
        method,
      };
    } else {
      return undefined;
    }

    console.log(options);
    return options;
  }

  get(...args) {
    let options = this._preprocessArgs('get', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[1] == 'object' &&
            args[1] &&
            args[1].constructor == Object,
          'args[1] must is JSON',
        );

        options = {
          ...args[1],
          url: args[0],
          method: 'get',
        };
        console.log(options);
      } else {
        assert(false, 'invaild args');
      }
    }
  }

  post(...args) {
    let options = this._preprocessArgs('post', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        options = {
          url: args[0],
          data: args[1],
          method: 'post',
        };

        console.log(options);
      } else if (args.length == 3) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[2] == 'object' &&
            args[2] &&
            args[2].constructor == Object,
          'args[2] must is JSON',
        );
        options = {
          ...args[2],
          url: args[0],
          data: args[1],
          method: 'post',
        };
        console.log(options);
      } else {
        assert(false, 'invaild argments');
      }
    }
  }

  delete(...args) {
    let options = this._preprocessArgs('delete', args);

    if (!options) {
      assert(typeof args[0] == 'string', 'args[0] must is string');
      assert(
        typeof args[1] == 'object' && args[1] && args[1].constructor == Object,
        'args[1] must is JSON',
      );

      options = {
        ...args[1],
        url: args[0],
        method: 'get',
      };

      console.log(options);
    }
  }
}

Axios.create = Axios.prototype.create = function(options = {}) {
  let axios = new Axios();

  let res = { ...JSON.parse(JSON.stringify(_default)) };

  merge(res, options);

  axios.default = res;
  return axios;
};

export default Axios.create();

复制代码

而后来测试一下

没问题,固然为何methodundefined,由于这时候还没走到我们的default呢,我们是在这决定默认请求值的,因此这里直接给个undefined就行,而后我们应该把些options全都和defaulft处理完,丢给我们的request函数去请求

而这个方法确定全部请求都须要,因此我们写一个公共方法

这个request方法主要干的事四件事

  1. 跟this.default进行合并
  2. 检测参数是否正确
  3. baseUrl 合并请求
  4. 正式调用request(options)
import _default from './default';
import { merge, assert } from './common';
import request from './request';

class Axios {
  constructor() {
    let _this = this;
    return new Proxy(request, {
      get(data, name) {
        return _this[name];
      },
      set(data, name, val) {
        _this[name] = val;

        return true;
      },
      apply(fn, thisArg, args) {
        let options = _this._preprocessArgs(undefined, args);
        if (!options) {
          if (args.length == 2) {
            assert(typeof args[0] == 'string', 'args[0] must is string');
            assert(
              typeof args[1] == 'object' &&
                args[1] &&
                args[1].constructor == Object,
              'args[1] must is JSON',
            );

            options = {
              ...args[1],
              url: args[0],
            };
            _this.request(options);
          } else {
            assert(false, 'invaild args');
          }
        }
      },
    });
  }

  _preprocessArgs(method, args) {
    let options;
    if (args.length == 1 && typeof args[0] == 'string') {
      options = { method, url: args[0] };
      this.request(options);
    } else if (args.length == 1 && args[0].constructor == Object) {
      options = {
        ...args[0],
        method,
      };
      this.request(options);
    } else {
      return undefined;
    }

    return options;
  }

  request(options) {
    console.log(options, 'request');
    // 1. 跟this.default进行合并
    // 2. 检测参数是否正确
    // 3. baseUrl 合并请求
    // 4. 正式调用request(options)
  }

  get(...args) {
    let options = this._preprocessArgs('get', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[1] == 'object' &&
            args[1] &&
            args[1].constructor == Object,
          'args[1] must is JSON',
        );

        options = {
          ...args[1],
          url: args[0],
          method: 'get',
        };
        this.request(options);
      } else {
        assert(false, 'invaild args');
      }
    }
  }

  post(...args) {
    let options = this._preprocessArgs('post', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        options = {
          url: args[0],
          data: args[1],
          method: 'post',
        };
        this.request(options);
      } else if (args.length == 3) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[2] == 'object' &&
            args[2] &&
            args[2].constructor == Object,
          'args[2] must is JSON',
        );
        options = {
          ...args[2],
          url: args[0],
          data: args[1],
          method: 'post',
        };
        this.request(options);
      } else {
        assert(false, 'invaild argments');
      }
    }
  }

  delete(...args) {
    let options = this._preprocessArgs('delete', args);

    if (!options) {
      assert(typeof args[0] == 'string', 'args[0] must is string');
      assert(
        typeof args[1] == 'object' && args[1] && args[1].constructor == Object,
        'args[1] must is JSON',
      );

      options = {
        ...args[1],
        url: args[0],
        method: 'get',
      };
      this.request(options);
    }
  }
}

Axios.create = Axios.prototype.create = function(options = {}) {
  let axios = new Axios();

  let res = { ...JSON.parse(JSON.stringify(_default)) };

  merge(res, options);

  axios.default = res;
  return axios;
};

export default Axios.create();

复制代码

这个合并很简单,我们以前写的merge函数又派上用场了,修改一下代码

...
request(options) {
    console.log(options);
    // 1. 跟this.default进行合并
    merge(options, this.default);

    console.log(options);
    // 2. 检测参数是否正确
    // 3. baseUrl 合并请求
    // 4. 正式调用request(options)
  }
  ...
复制代码

这时候能够看到,合并前和合并后数据就已经都有了,可是,这时候我们的header就不该该是所有都有了,应该根据传过来的method是什么来把对应的方式的headercommon合并一下

request(options) {
    // 1. 跟this.default进行合并
    let _headers = this.default.headers;
    delete this.default.headers;
    merge(options, this.default);
    this.default.headers = _headers;
    //合并头
    // this.default.headers.common -> this.default.headers.get -> options
    let headers = {};
    merge(headers, this.default.headers.common);
    merge(headers, this.default.headers[options.method.toLowerCase()]);
    merge(headers, options.headers);

    console.log(headers);
    console.log(options);

    // 2. 检测参数是否正确
    // 3. baseUrl 合并请求
    // 4. 正式调用request(options)
  }
复制代码

这里比较乱,因此我们先来捋一捋

我们目的是要让header合并,不过合并的话就会有点小问题,以前在们在default的里定义的commonget...也都会被复制过来,若是要是我们用if判断 options.header.common == this.default.headers.common而后delete的话,这时候你会发现不行,由于我们也知道,你直接写两个对象判断就至关于直接new了两个对象,那这时候判断确定不相等,那么我们是在何时复制的呢

就在我们封装的merge里,以及还有不少地方都动过这个东西

而后,我们应该找到这个东西到底在何时就不同了,其实也就是我们这个request函数里第一次merge的时候

因此我们这里玩了一个小技巧,由于common这些东西在底下都已经手动搞了,因此这里不须要他复制进来,因此先delete了一下

让他在以前就进不去,delete以后再给拿回去,两头不耽误,真好~

最后,我们把headers赋值到我们的options.headers

request(options) {
    // 1. 合并头
    let _headers = this.default.headers;
    delete this.default.headers;
    merge(options, this.default);
    this.default.headers = _headers;

    // this.default.headers.common -> this.default.headers.get -> options
    let headers = {};
    merge(headers, this.default.headers.common);
    merge(headers, this.default.headers[options.method.toLowerCase()]);
    merge(headers, options.headers);

    options.headers = headers;
    console.log(options);

    // 3. baseUrl 合并请求
    

    // 4. 正式调用request(options)
  }
复制代码

~ index.js

import Axios from './axios';

Axios('1.php');
Axios({
  url: '2.php',
  params: { a: 12, b: 3 },
  headers: {
    a: 12,
  },
});

复制代码

测试一下结果

能够看到,没毛病~

而后我们再来看一下第二步,其实这个校验我们能够写的很是很是多值得校验的事儿,可是这里只说明意思,写几个就算,你们有兴趣能够多补一些

...
assert(options.method, 'no method');
assert(typeof options.method == 'string', 'method must be string');
assert(options.url, 'no url');
assert(typeof options.url == 'string', 'url must be string');
...
复制代码

第三步也是直接上代码了

~ axios.js

options.url=options.baseUrl+options.url;
delete options.baseUrl; 
复制代码

~ common.js

export function assert(exp, msg = 'assert faild') {
  if (!exp) {
    throw new Error(msg);
  }
}

export function merge(dest, src) {
  for (let name in src) {
    if (typeof src[name] == 'object') {
      if (!dest[name]) {
        dest[name] = {};
      }

      merge(dest[name], src[name]);
    } else {
      if (dest[name] === undefined) {
        dest[name] = src[name];
      }
    }
  }
}
复制代码

~ index.js

import Axios from './axios';

Axios('1.php', {
  baseUrl: 'http://www.baidu.com/',
  headers: {
    a: 12,
  },
});
Ï
复制代码

这时候再测试一下,能够看到,就没问题了

这里说明一下为何要改一下merge,加了一个判断,由于我们以前是直接替换掉,有没有都替换,这确定不行,不加上的话会把我们的baseUrl干了

固然了,还有个小事儿我们须要处理一下,若是用你这个库的人脑子有病(固然,不考虑脑子有病的也能够),他写路径的时候是这么写的

你这个是否是又不行了呀,很简单,NodeJS里有一个url包,能够直接引过来用,webpack会帮你把他打包起来,不过有一点须要注意,webpack不是全部的东西都能打包的,好比fs模块,甚至一些底层功能用cc++写的系统包这确定就不行,不过一个url问题不大

~ axios.js

import _default from './default';
import { merge, assert } from './common';
import request from './request';
const urlLib = require('url');

class Axios {
  constructor() {
    let _this = this;
    return new Proxy(request, {
      get(data, name) {
        return _this[name];
      },
      set(data, name, val) {
        _this[name] = val;

        return true;
      },
      apply(fn, thisArg, args) {
        let options = _this._preprocessArgs(undefined, args);
        if (!options) {
          if (args.length == 2) {
            assert(typeof args[0] == 'string', 'args[0] must is string');
            assert(
              typeof args[1] == 'object' &&
                args[1] &&
                args[1].constructor == Object,
              'args[1] must is JSON',
            );

            options = {
              ...args[1],
              url: args[0],
            };
            _this.request(options);
          } else {
            assert(false, 'invaild args');
          }
        }
      },
    });
  }

  _preprocessArgs(method, args) {
    let options;
    if (args.length == 1 && typeof args[0] == 'string') {
      options = { method, url: args[0] };
      this.request(options);
    } else if (args.length == 1 && args[0].constructor == Object) {
      options = {
        ...args[0],
        method,
      };
      this.request(options);
    } else {
      return undefined;
    }

    return options;
  }

  request(options) {
    // 1. 合并头
    let _headers = this.default.headers;
    delete this.default.headers;
    merge(options, this.default);
    this.default.headers = _headers;

    // this.default.headers.common -> this.default.headers.get -> options
    let headers = {};
    merge(headers, this.default.headers.common);
    merge(headers, this.default.headers[options.method.toLowerCase()]);
    merge(headers, options.headers);

    options.headers = headers;
    console.log(options);

    // 2. 检测参数是否正确
    assert(options.method, 'no method');
    assert(typeof options.method == 'string', 'method must be string');
    assert(options.url, 'no url');
    assert(typeof options.url == 'string', 'url must be string');

    // 3. baseUrl 合并请求
    options.url = urlLib.resolve(options.baseUrl, options.url);
    delete options.baseUrl;

    // 4. 正式调用request(options)
    request(options);
  }

  get(...args) {
    let options = this._preprocessArgs('get', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[1] == 'object' &&
            args[1] &&
            args[1].constructor == Object,
          'args[1] must is JSON',
        );

        options = {
          ...args[1],
          url: args[0],
          method: 'get',
        };
        this.request(options);
      } else {
        assert(false, 'invaild args');
      }
    }
  }

  post(...args) {
    let options = this._preprocessArgs('post', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        options = {
          url: args[0],
          data: args[1],
          method: 'post',
        };
        this.request(options);
      } else if (args.length == 3) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[2] == 'object' &&
            args[2] &&
            args[2].constructor == Object,
          'args[2] must is JSON',
        );
        options = {
          ...args[2],
          url: args[0],
          data: args[1],
          method: 'post',
        };
        this.request(options);
      } else {
        assert(false, 'invaild argments');
      }
    }
  }

  delete(...args) {
    let options = this._preprocessArgs('delete', args);

    if (!options) {
      assert(typeof args[0] == 'string', 'args[0] must is string');
      assert(
        typeof args[1] == 'object' && args[1] && args[1].constructor == Object,
        'args[1] must is JSON',
      );

      options = {
        ...args[1],
        url: args[0],
        method: 'get',
      };
      this.request(options);
    }
  }
}

Axios.create = Axios.prototype.create = function(options = {}) {
  let axios = new Axios();

  let res = { ...JSON.parse(JSON.stringify(_default)) };

  merge(res, options);

  axios.default = res;
  return axios;
};

export default Axios.create();

复制代码

这时候再测试一下

就没问题了

merge 问题

其实我们这个merge如今还有很大的问题,由于我们最开始的想要的功能和我们如今的功能有差入

我们如今是被迫写了一个if,改为了查漏补缺,那么其实后面有优先级的东西顺序应该都反过来

我们最开始的需求是什么,是想让这个dest优先级最低,是能够被别人覆盖的,可是如今写了这个if以后,变成他优先级最高了,那这个就不对,可是还不能去掉,去掉了以后这个合并就又出问题了

其实应该怎么作,应该把这两个东西顺序颠倒一下,可是这时候又不对了,由于就致使这个 this.default变了,这时候又须要复制一份,我们来写一个公共的方法放到 common.js

~ common.js

...

export function clone(obj) {
  return JSON.parse(JSON.stringify(obj));
}

复制代码

以及我们这个顺序稍微颠倒一下,而后数据克隆一下

这个时候来测试一下

会发现是否是header里的这些东西又回来了呀,缘由也很简单,由于我们在上面整个的defaultclone下来了,因此我们把delete这一块往上提提

这时候就没问题了


request

这个时候我们就应该来写第四步了 直接把options给到我们的request函数里就能够,零零碎碎的问题全都给它处理好了

而后来修改一下request.js

~ request.js

export default function request(options) {
  console.log(options);
  let xhr = new XMLHttpRequest();

  xhr.open(options.method, options.url, true);

  for (let name in options.headers) {
    xhr.setRequestHeader(name, options.headers[name]);
  }

  xhr.send(options.data);
}
复制代码

暂时先写一个简单的请求,而后我们来测试一下看看能不能发出去

首先先建一个txt,方便我们测试,我就放到data目录里了,像这样

而后改一下index.js

~ index.js

import Axios from './axios';

Axios('/data/1.txt', {
  headers: {
    a: 12,
  },
});

复制代码

这时候我们能够看到,header是加上了的,而且返回的东西也对

固然这个东西还没完,也是一个防止用户捣乱的事

若是用户给你的header是这样的东西,那这个就不太好,因此最好仍是给它来一个编码

~ request.js

export default function request(options) {
  console.log(options);
  let xhr = new XMLHttpRequest();

  xhr.open(options.method, options.url, true);

  for (let name in options.headers) {
    xhr.setRequestHeader(
      encodeURIComponent(name),
      encodeURIComponent(options.headers[name]),
    );
  }

  xhr.send(options.data);
}

复制代码

这就没问题了万一后台有点问题,一遇见冒号就算结束这种问题就能避免了

而后我们用的时候确定更多的时候是须要一个asyncawait一下,因此我们须要用Promise来包一下

~ axios.js

export default function request(options) {
  console.log(options);
  let xhr = new XMLHttpRequest();

  xhr.open(options.method, options.url, true);

  for (let name in options.headers) {
    xhr.setRequestHeader(
      encodeURIComponent(name),
      encodeURIComponent(options.headers[name]),
    );
  }

  xhr.send(options.data);

  return new Promise((resolve, reject) => {
    xhr.onreadystatechange = function() {
      if (xhr.readyState == 4) {
        if (xhr.status >= 200 && xhr.status < 300) {
          resolve(xhr);
        } else {
          reject(xhr);
        }
      }
    };
  });
}

复制代码

以及这个时候,我们还有不少不少问题

  1. 304其实也算成功,那这么封装也是就不对,并且用户可能有一些自定义的code,这个怎么作到配置
  2. 我们目前的webpack只能兼容es6asyncawait是兼容不了的,这个该怎么配

我们先来解决webpack的问题,这个其实很简单,我们须要再按一个包 yarn add @babel/polyfill

而后打开webpack.config.js修改一下entry

~ webpack.config.js

...
entry: ['@babel/polyfill','./src/index.js'],
...
复制代码

注意这个顺序是不能颠倒的

这样就能够兼容了,而后我们再来修改一下index.js

import Axios from './axios';

(async () => {
  let res = await Axios('/data/1.txt', {
    headers: {
      a: 12,
      b: '321fho:fdsf vfds; : ',
    },
  });

  console.log(res);
})();
复制代码

能够看到结果是undefined,这是由于我们根本没有返回我们的处理结果

这时候修改一下axios.js

import _default from './default';
import { merge, assert, clone } from './common';
import request from './request';
const urlLib = require('url');

class Axios {
  constructor() {
    let _this = this;
    return new Proxy(request, {
      get(data, name) {
        return _this[name];
      },
      set(data, name, val) {
        _this[name] = val;

        return true;
      },
      apply(fn, thisArg, args) {
        let options = _this._preprocessArgs(undefined, args);
        if (!options) {
          if (args.length == 2) {
            assert(typeof args[0] == 'string', 'args[0] must is string');
            assert(
              typeof args[1] == 'object' &&
                args[1] &&
                args[1].constructor == Object,
              'args[1] must is JSON',
            );

            options = {
              ...args[1],
              url: args[0],
            };
            return _this.request(options);
          } else {
            assert(false, 'invaild args');
          }
        }
      },
    });
  }

  _preprocessArgs(method, args) {
    let options;
    if (args.length == 1 && typeof args[0] == 'string') {
      options = { method, url: args[0] };
      this.request(options);
    } else if (args.length == 1 && args[0].constructor == Object) {
      options = {
        ...args[0],
        method,
      };
      this.request(options);
    } else {
      return undefined;
    }

    return options;
  }

  request(options) {
    // 1. 合并头
    let _headers = this.default.headers;
    delete this.default.headers;

    let result = clone(this.default);
    merge(result, this.default);
    merge(result, options);
    this.default.headers = _headers;

    options = result;

    // this.default.headers.common -> this.default.headers.get -> options
    let headers = {};
    merge(headers, this.default.headers.common);
    merge(headers, this.default.headers[options.method.toLowerCase()]);
    merge(headers, options.headers);

    options.headers = headers;

    // 2. 检测参数是否正确
    assert(options.method, 'no method');
    assert(typeof options.method == 'string', 'method must be string');
    assert(options.url, 'no url');
    assert(typeof options.url == 'string', 'url must be string');

    // 3. baseUrl 合并请求
    options.url = urlLib.resolve(options.baseUrl, options.url);
    delete options.baseUrl;

    // 4. 正式调用request(options)
    return request(options);
  }

  get(...args) {
    let options = this._preprocessArgs('get', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[1] == 'object' &&
            args[1] &&
            args[1].constructor == Object,
          'args[1] must is JSON',
        );

        options = {
          ...args[1],
          url: args[0],
          method: 'get',
        };
        return this.request(options);
      } else {
        assert(false, 'invaild args');
      }
    }
  }

  post(...args) {
    let options = this._preprocessArgs('post', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        options = {
          url: args[0],
          data: args[1],
          method: 'post',
        };
        return this.request(options);
      } else if (args.length == 3) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[2] == 'object' &&
            args[2] &&
            args[2].constructor == Object,
          'args[2] must is JSON',
        );
        options = {
          ...args[2],
          url: args[0],
          data: args[1],
          method: 'post',
        };
        return this.request(options);
      } else {
        assert(false, 'invaild argments');
      }
    }
  }

  delete(...args) {
    let options = this._preprocessArgs('delete', args);

    if (!options) {
      assert(typeof args[0] == 'string', 'args[0] must is string');
      assert(
        typeof args[1] == 'object' && args[1] && args[1].constructor == Object,
        'args[1] must is JSON',
      );

      options = {
        ...args[1],
        url: args[0],
        method: 'get',
      };
      return this.request(options);
    }
  }
}

Axios.create = Axios.prototype.create = function(options = {}) {
  let axios = new Axios();

  let res = { ...JSON.parse(JSON.stringify(_default)) };

  merge(res, options);

  axios.default = res;
  return axios;
};

export default Axios.create();

复制代码

这时候我们再看结果,是否是就能够了,固然了,我们确定不能就直接把原始的xml对象返回回去,我们还要对返回的数据进行各类处理


处理数据

我们先来简单的改一下axios.js 下的request

返回又一个promise,这时候能够看到结果,一点毛病没有

可是我们这个东西全放在axios.js里就太乱了,因此我们也单独把它拆除去

建两个文件一个是/src/response.js一个是/src/error.js

而后这里axios.js引入一下,而且处理的时候分别交给他们

而后response.js里直接返回值就能够了

不过这个headers稍微有点特别,须要单独调一个方法xhr.getAllResponseHeaders(),不过这返回的是原始xhr的头,这确定不行,因此我们须要来切一下

~ response.js

export default function(xhr) {
  let arr = xhr.getAllResponseHeaders().split('\r\n');
  let headers = {};

  arr.forEach(str => {
    if (!str) return;
    let [name, val] = str.split(': ');
    headers[name] = val;
  });

  return {
    ok: true,
    status: xhr.status,
    statusText: xhr.statusText,
    data: xhr.response,
    headers,
    xhr,
  };
}

复制代码

这样就好了

transformRequest && transformResponse

这时候固然还不算完,由于我们如今的data尚未任何处理,因此确定都是字符串,以及用户可能自定义这个处理方式,熟悉axios的朋友应该知道,axiostransformRequesttransformResponse方法

这时候我们先来改一下以前axios.js里的request方法

如今须要在第三步和第四步之间来处理一下请求

先把参数打印一下,而后修改一下index.js的测试demo

为了测试方便我把1.txt改为1.json了,方便我们一会处理json数据好看到效果

能够看到,这个参数是能够拿到的,那么接下来就比较简单了,直接来

这时候看请求,这个headers就加上了

这里稍微提一嘴,为何我要delete掉,虽然不delete没什么关系,可是我但愿我这个request保持干净

至于这个自定义返回结果,是否是就更简单了呀

这时候能够看眼结果,我没传transformResponse,结果是这样

这时候就能够了

固然了,我们如今已经能够很灵活的运用了,不止单传这个json里能够配参数,全局配置统一处理同样是能够的,我们来试试

以及不一样的实例之间,都是能够的


拦截器

拦截器在一个请求库里确定是必不可少的,其实我们这个库写到如今,想加一个这玩意,实际上是很容易的

~ index.js

import Axios from './axios';

Axios.interceptors.request.use(function(config) {
  config.headers.interceptors = 'true';
  return config;
});

(async () => {
  let res = await Axios('/data/1.json', {
    headers: {
      a: 12,
      b: '321fho:fdsf vfds; : ',
    },
  });

  console.log(res);
})();

复制代码

而后新建一个interceptor.js

~interceptor.js

class Interceptor {
  constructor() {
    this._list = [];
  }

  use(fn) {
    this._list.push(fn);
  }

  list() {
    return this._list;
  }
}


export default Interceptor;
复制代码

~ axios.js

import _default from './default';
import { merge, assert, clone } from './common';
import request from './request';
import createResponse from './response';
import createError from './error';
const urlLib = require('url');
import Interceptor from './interceptor';

class Axios {
  constructor() {
    this.interceptors = {
      request: new Interceptor(),
      response: new Interceptor(),
    };

    let _this = this;
    return new Proxy(request, {
      get(data, name) {
        return _this[name];
      },
      set(data, name, val) {
        _this[name] = val;

        return true;
      },
      apply(fn, thisArg, args) {
        let options = _this._preprocessArgs(undefined, args);
        if (!options) {
          if (args.length == 2) {
            assert(typeof args[0] == 'string', 'args[0] must is string');
            assert(
              typeof args[1] == 'object' &&
                args[1] &&
                args[1].constructor == Object,
              'args[1] must is JSON',
            );

            options = {
              ...args[1],
              url: args[0],
            };
            return _this.request(options);
          } else {
            assert(false, 'invaild args');
          }
        }
      },
    });
  }

  _preprocessArgs(method, args) {
    let options;
    if (args.length == 1 && typeof args[0] == 'string') {
      options = { method, url: args[0] };
      this.request(options);
    } else if (args.length == 1 && args[0].constructor == Object) {
      options = {
        ...args[0],
        method,
      };
      this.request(options);
    } else {
      return undefined;
    }

    return options;
  }

  request(options) {
    // 1. 合并头
    let _headers = this.default.headers;
    delete this.default.headers;

    let result = clone(this.default);
    merge(result, this.default);
    merge(result, options);
    this.default.headers = _headers;

    options = result;

    // this.default.headers.common -> this.default.headers.get -> options
    let headers = {};
    merge(headers, this.default.headers.common);
    merge(headers, this.default.headers[options.method.toLowerCase()]);
    merge(headers, options.headers);

    options.headers = headers;

    // 2. 检测参数是否正确
    assert(options.method, 'no method');
    assert(typeof options.method == 'string', 'method must be string');
    assert(options.url, 'no url');
    assert(typeof options.url == 'string', 'url must be string');

    // 3. baseUrl 合并请求
    options.url = urlLib.resolve(options.baseUrl, options.url);
    delete options.baseUrl;

    // 4. 变换一下请求
    const { transformRequest, transformResponse } = options;
    delete options.transformRequest;
    delete options.transformResponse;

    if (transformRequest) options = transformRequest(options);

    let list = this.interceptors.request.list();
    list.forEach(fn => {
      options = fn(options);
    });

    // 5. 正式调用request(options)
    return new Promise((resolve, reject) => {
      return request(options).then(
        xhr => {
          let res = createResponse(xhr);
          if (transformResponse) res = transformResponse(res);
          
          let list = this.interceptors.response.list();
          list.forEach(fn => {
            res = fn(res);
          });
          resolve(res);
        },
        xhr => {
          let err = createError(xhr);
          reject(err);
        },
      );
    });
  }

  get(...args) {
    let options = this._preprocessArgs('get', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[1] == 'object' &&
            args[1] &&
            args[1].constructor == Object,
          'args[1] must is JSON',
        );

        options = {
          ...args[1],
          url: args[0],
          method: 'get',
        };
        return this.request(options);
      } else {
        assert(false, 'invaild args');
      }
    }
  }

  post(...args) {
    let options = this._preprocessArgs('post', args);

    if (!options) {
      if (args.length == 2) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        options = {
          url: args[0],
          data: args[1],
          method: 'post',
        };
        return this.request(options);
      } else if (args.length == 3) {
        assert(typeof args[0] == 'string', 'args[0] must is string');
        assert(
          typeof args[2] == 'object' &&
            args[2] &&
            args[2].constructor == Object,
          'args[2] must is JSON',
        );
        options = {
          ...args[2],
          url: args[0],
          data: args[1],
          method: 'post',
        };
        return this.request(options);
      } else {
        assert(false, 'invaild argments');
      }
    }
  }

  delete(...args) {
    let options = this._preprocessArgs('delete', args);

    if (!options) {
      assert(typeof args[0] == 'string', 'args[0] must is string');
      assert(
        typeof args[1] == 'object' && args[1] && args[1].constructor == Object,
        'args[1] must is JSON',
      );

      options = {
        ...args[1],
        url: args[0],
        method: 'get',
      };
      return this.request(options);
    }
  }
}

Axios.create = Axios.prototype.create = function(options = {}) {
  let axios = new Axios();

  let res = clone(_default);

  merge(res, options);

  axios.default = res;
  return axios;
};

export default Axios.create();

复制代码

能够看到,基本上是同样的套路,主要就是把参数传过来而后调用一下而已

不过这里面有两个小小的问题须要处理

  1. 我们如今给用户开了不少口,若是他要是返回的config不对或者没返回我们理应给他返回错误信息,再校验一下,这时候你们应该能想到了,应该吧axios校验这些参数单独搞一个函数,没什么技术含量,这里就很少赘述,你们有兴趣能够试一下
  2. 我们给用户的是函数,用的是forEach,这时候就会致使一个问题,若是用户给你的是一个带async的函数那就不行了,我们也要加上asyncawait,不过asyncawait里面返回一个promise又很怪,这个你们有兴趣能够本身试试,或者评论区留言

这时候来试一下效果

能够看到我们这个拦截器也算是完成了

总结

本篇最终代码已上传github,连接以下 github.com/Mikey-9/axi…

仍是那句话,我们本篇文章主要不是要完整的实现axios,而是实现一个这样的库的思想,固然其中也有不少问题,欢迎你们在评论区留言或者能够加个人qq或者微信一块儿交流

篇幅略长,感谢观看

Thank You

qq:

微信:

相关文章
相关标签/搜索