編寫一個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:

微信:

相關文章
相關標籤/搜索