preface
This article needs to have a basic understanding of JS, ES6, Webpack, network request and other basic knowledge
I’m sure you’ve all used Axios or some other library for web requests, ajax, fly.js, etc. I’ve only used seven or eight libraries, all of which are similar
This article is not intended to implement everything axios does word for word, it doesn’t make sense, but it will. The main point is to get a feel for the flow and structure, how the project is extensible, testable, readable, etc
Don’t talk nonsense, open do ~
Set up the project
The old rule is to create an empty directory, then open the command line to execute
yarn init -y
or
cnpm init -y
webpack
Yarn Global add webpack webpack- CLI – yarn Global add webpack webpack- CLI
Installing dependency packages
These packages are mainly needed to execute commands, help compile and debug, and Babel helps to be compatible with browsers as much as possible. After all, we must be full of ES6 code
yarn add webpack webpack-cli webpack-dev-server babel-loader @babel/core @babel/preset-env
Configuration webpack
Then create webpack.config.js in the root directory to configure webPack, and create a SRC directory to store the library code. The current directory will look like this
The first simple configuration, the follow-up needs to add, here directly on the code
~ 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'}; };Copy the code
In SRC, create an index.js file and write something like this
The terminal then executes the webpack command
Of course, it is incompatible now, or we can not use Babel in the beginning, we can try, such as I now index.js add a sentence
And then when you compile it, you see the result is still let, which is definitely not going to work
Okay, so now we’re going to configure Babel, there’s nothing to talk about, I’m just going to put the code here, there’s nothing to talk about, okay
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'],},},},],},}; };Copy the code
And then, you don’t want to manually go to webpack every time you make a change, right? Bring in 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,}}; };Copy the code
If you run webpack-dev-server in the terminal, it will automatically find the global module, which is not good, so… You know,
Add commands directly to 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"}}Copy the code
Then the yarn start
An HTML pops up
Of course, the default is to go to the root of index. HTML, which we don’t have, so create one under the root and import our axios.js
<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>
Copy the code
Refresh the page to see the SRC /index.js alert in effect
In addition, webpack-dev-server is also available, and the page will be refreshed automatically if the code is changed
Then, let’s match build
So without further ado, let’s go straight to the code
~ 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"}}Copy the code
~ 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,}}; };Copy the code
It’s ok to see it
Good to this I was the Webpack related things to build almost, the next is going to start busy ~
Axios project code
Let’s start by creating common.js, which holds common methods, with an assertion
~ /src/common.js
export function assert(exp, msg = 'assert faild') {
if(! exp) {throw new Error(msg); }}Copy the code
Then create another file (personal habit)/SRC /axios and put the main file here
Then let’s see if we can use axios({… }) or axios.get, etc
In index.js, write the result directly, write the expected usage, and then add the internal
~ 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,}});Copy the code
Then will think about it, we can directly write function, this is no problem, but it is too loose, people don’t like it, but also can be, so here I am in class, because the output into a class out certainly is an instance, since is the instance, then certainly will not run directly as a function () directly
Js class constructor can return something. If you are not familiar with this thing, it is recommended to see the js class first. Here is not much to say, mainly explain the idea
In simple terms, we can return a proxy object, so that we can write directly as a class, or call directly as a function ()
And let’s print it out first
~
If YOU look at the console of the page, you can see that AXIos is a proxy object, like this
You can also see an error because we are now returning a proxy object, not an instance class, and there is no get
If you’re listening to a proxy, it’s important to know what you’re using to create a proxy. If you’re listening to an object, That still can’t be called directly, but if you want to call it directly like a function, then you have to be listening to a function
Like this,
So let’s solve the problem that we can’t find a get function, so let’s add a method to the proxy, it’s very simple, you can add a get method to the proxy, and if someone comes to it, they’ll just return it from my class, and that’s it, but pay a little bit of attention to this, because if I write this, it’s going to refer to the 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;
Copy the code
If you look at this, no errors are reported, and get and POST console
At this point we can proceed to write the data request… ? Is far from enough
There’s a lot of variety in axios arguments, so let’s just kind of summarize
axios('1.txt',{})
axios({
url: '1.txt',
method
})
axios.get('1.txt')
axios.get('1.txt',{})
axios.post....
Copy the code
Wait a minute, this point, how can you unify the parameters of such a complex multi-source
The second thing is that AXIos can customize parameters very deeply, either globally or individually, interceptors, transfrom, etc., defaults, etc.
parameter
Let’s start with a default to define the default values, and let me just say that this x-request-by is sort of an unwritten specification, and it’s something that most Request libraries are willing to do, so that the backend can determine whether your Request is coming from Ajax or from or from a browser 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;
Copy the code
Of course, the diagram here is simple, simple to write a few parameters, you can like to add a lot of things, such as the default value of data and so on, enough for the first time, the follow-up is not enough to add
Default = _default, of course not, because our axios will eventually need multiple instances of axios. Create, then it will not work. Prototype, of course
Parse (json.stringify (_default)), which is the highest performance method, and then modify the code slightly
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();
Copy the code
Here we add a create method to both the prototype and the instance, because we can use axios.create() or axios() directly, and neither static method nor instance method will satisfy our needs
So let’s try it out. Let’s console axios.default
You’ll notice that undefined is already added here
Because at this point this axios is not an object, it’s a proxy, and we haven’t added a set method to the proxy yet, right
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();
Copy the code
If you look in your browser, you’ll see that default is already there
And let’s create two axios, change the parameters and try it out
The two instance parameters don’t matter, so that’s a nice NTH step, and we’re about a quarter of the way through AxiOS
We now have no effect between instances, but when we change parameters, we should not only change axios.default. XXX directly, we should also have parameters, like this
Here we can modify the axios.create method directly
~ axios.js
. Axios.create = Axios.prototype.create =function(options={}) {
let axios = newAxios(); axios.default = { ... JSON.parse(JSON.stringify(_default)), ... options };returnaxios; }; .Copy the code
I can just expand it and replace it, right? But, really?
Suppose we pass an object directly, there is a word of headers is directly to our header the default parameters have to be replaced the ah, that this is not so good, of course, this also see we the library needs to himself, if we want to do, is to do not have what problem, the problem is as follows
So at this point, we can do it with a little recursion
~ axios.js
. Axios.create = Axios.prototype.create =function(options = {}) {
let axios = new Axios();
letres = { ... 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;
returnaxios; }; .Copy the code
Look at it at this point, there’s no problem
Code defragmenting
Next, let’s not rush to write the request parameters, let’s first plan this code a little planning, tidy up, after all, all put in a file, this later can not maintain
The current split can be divided into several points
default
Is it possible to use a separate file to install- this
merge
The functions must be common, so we can put them in ourcommon.js
In the - this
request
It should also be put in a separate onejs
To define the
Without further ado, let’s get right to the code
~ request.js
export default function request() {}Copy the code
~ default.js
export default {
method: 'get'.baseUrl: ' '.headers: {
common: {
'X-Request-By': 'XMLHttpRequest',},get: {},
post: {},
delete: {},}};Copy the code
~ 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]; }}}Copy the code
~ 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();
letres = { ... JSON.parse(JSON.stringify(_default)) };
merge(res, options);
axios.default = res;
return axios;
};
export default Axios.create();
Copy the code
It feels so much cleaner now, doesn’t it
Processing request parameters
Before writing, let’s take a look at what axios supports and then think about how to write it
Roughly speaking, except for the total axios({… }) are these three methods similar? Of course, there are too many things in Axios, so here is a simple implementation of these three, mainly to explain the problem, you can add your own, but only manual work
We can see that there are a lot of axios arguments, so we should just unify them. Whatever argument is passed, we should return axios({}), and finally unify them. Isn’t that convenient
So let’s just look at the first two cases
You’ll notice that the method in the first two cases is the same except for this one, so we can pull out a method to deal with it in a unified way
~ 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;
}
returnoptions; } 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();
letres = { ... JSON.parse(JSON.stringify(_default)) };
merge(res, options);
axios.default = res;
return axios;
};
export default Axios.create();
Copy the code
Then at this time, we in order to encapsulate a library perspective, certainly need to carry out a variety of parameters verification, type and so on, if not to give him a serious error, to help users to debug
We used assert in common.js
. 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',); }}}...Copy the code
The rules here correspond to the way axios is used as written above, which is pretty much the same. Now that these parameters are verified, we can write the specific personalized processing
By the way, this place of course can also be reused, but there is no need to make a pass actually did not reduce much code, and disorderly, also depends on the individual, we do not like to modify their own
Then let’s deal with the options and 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);
returnoptions; } 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();
letres = { ... JSON.parse(JSON.stringify(_default)) };
merge(res, options);
axios.default = res;
return axios;
};
export default Axios.create();
Copy the code
So let’s test that out
~ 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}});Copy the code
And you can see that it’s okay, right?
And then… Not forgetting, we also need to deal with the case of direct apply, when direct Axios() is called this way
No nonsense, go directly to the code, just like 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);
returnoptions; } 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();
letres = { ... JSON.parse(JSON.stringify(_default)) };
merge(res, options);
axios.default = res;
return axios;
};
export default Axios.create();
Copy the code
So let’s test that out
Method = undefined (default); method = undefined (default); method = undefined (default); Throw it to our request function
This method must be required for all requests, so let’s write a public method
This request method does four main things
- Merge with this.default
- Check whether the parameters are correct
- BaseUrl merge request
- Formally calling 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. Merge with this.default
// 2. Check whether the parameters are correct
// 3. BaseUrl merge request
// 4. Formally call 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();
letres = { ... JSON.parse(JSON.stringify(_default)) };
merge(res, options);
axios.default = res;
return axios;
};
export default Axios.create();
Copy the code
Merge this merge is very simple, we wrote earlier to use the merge function again, modify the code
. request(options) {console.log(options);
// 1. Merge with this.default
merge(options, this.default);
console.log(options);
// 2. Check whether the parameters are correct
// 3. BaseUrl merge request
// 4. Formally call request(options)}...Copy the code
In this case, we can see that the data before and after the merge already have, but in this case, we should not have all the headers. We should merge the corresponding header and common according to the method that is passed
request(options) {
// 1. Merge with this.default
let _headers = this.default.headers;
delete this.default.headers;
merge(options, this.default);
this.default.headers = _headers;
/ / merge head
// 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. Check whether the parameters are correct
// 3. BaseUrl merge request
// 4. Formally call request(options)
}
Copy the code
It’s a little messy here, so let’s get this straight
We want to merge the headers, but there is a bit of a problem with merging them. We defined common, get… Also will be copied, if if if we use to judge options.header.com mon mon = = this.default.headers.com and then delete it, then you will find no, because we also know, If you write two objects directly, it is equivalent to new two objects directly, then the judgment must not be equal, so when did we copy
It’s in the merge that we encapsulate, and there are a lot of other places that have touched this thing
Then, we should find out when this thing is different, in fact, when we merge our request function for the first time
So here we play a little trick, because the common stuff is already done manually at the bottom, so there is no need for him to copy in, so we delete it first
Let him in before can not enter, delete after to take back, both ends do not delay, good ~
Finally, let’s assign headers to our options.headers
request(options) {
// 1. Merge header
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 merge request
// 4. Formally call request(options)
}
Copy the code
~ index.js
import Axios from './axios';
Axios('1.php');
Axios({
url: '2.php'.params: { a: 12.b: 3 },
headers: {
a: 12,}});Copy the code
Test the results
As you can see, there is no problem
And then let’s look at the second step, in fact, this check we can write very, very many things worth checking, but this is just to illustrate the meaning, just write a few, you can add more if you are interested
. 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'); .Copy the code
The third step is also directly to the code
~ axios.js
options.url=options.baseUrl+options.url;
delete options.baseUrl;
Copy the code
~ 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]; }}}}Copy the code
~ index.js
import Axios from './axios';
Axios('1.php', {
baseUrl: 'http://www.baidu.com/'.headers: {
a: 12,}}); i.Copy the code
And when you test it again, you can see that it’s fine
Merge, merge, merge, merge, merge, merge, merge, merge, merge, merge, merge, merge, merge, merge, merge
Of course, there is a small matter that we need to deal with, if the person using your library is mentally ill (of course, it is not considered mentally ill), he wrote the path in this way
NodeJS has a url package that you can use directly, and WebPack will pack it for you. However, it is important to note that webPack can not pack everything, such as FS module, or even some low-level functions written in C and C ++ system package. But a URL is not a problem
~ 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. Merge header
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. Check whether the parameters are correct
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 merge request
options.url = urlLib.resolve(options.baseUrl, options.url);
delete options.baseUrl;
// 4. Formally call 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();
letres = { ... JSON.parse(JSON.stringify(_default)) };
merge(res, options);
axios.default = res;
return axios;
};
export default Axios.create();
Copy the code
Test again at this point
It’s ok
The merge problem
There is still a big problem with this merge, because there is a difference between what we originally wanted and what we have now
We are now forced to write an “if” instead of “check for a missing”, so everything that has priority should be in reverse order
What is our initial requirement? We want the dest to have the lowest priority, so that it can be overwritten by others. But now after we write this if, it becomes the highest priority, so this is not correct, but we can not remove it, and then the merge will have problems again
this.default
common.js
~ common.js
. exportfunction clone(obj) {
return JSON.parse(JSON.stringify(obj));
}
Copy the code
And let’s reverse the order a little bit and clone the data
Let’s test it out at this point
It will be found that the items in the header come back again. The reason is also simple, because we have cloned the whole default in the header, so let’s lift the delete part up
That’s when it’s ok
request
At this point, we should write the fourth step directly into the options of our request function, it can handle all the bits and pieces
Then modify 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);
}
Copy the code
Write a simple request for now, and then let’s test it out and see if it can be sent
First build a TXT, convenient for our test, I put in the data directory, like this
Then change index.js
~ index.js
import Axios from './axios';
Axios('/data/1.txt', {
headers: {
a: 12,}});Copy the code
Now, we can see that the header is added, and we’re returning the right thing
Of course, this thing is not finished, but also a matter of preventing users from making trouble
If the user gives you a header that looks like this, that’s not a good idea, so you might as well code it
~ 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);
}
Copy the code
So that’s fine if there’s a problem in the background, if there’s a colon, that’s the end of the problem
Then when we use it, we definitely need an async and await more, so we need to use Promise to package it
~ 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); }}}; }); }Copy the code
And at this time, we have many, many questions
- In fact, the 304 is also successful, so the packaging is not correct, and the user may have some custom
code
How do you configure this - Our current
webpack
Can only be compatiblees6
.async
andawait
It’s not compatible. How do I match this
Let’s solve the webpack problem first, this is actually very simple, we need to press another package yarn add @babel/polyfill
Then open webpack.config.js to modify entry
~ webpack.config.js
. entry: ['@babel/polyfill'.'./src/index.js'],...Copy the code
Note that this order cannot be reversed
Now that it’s compatible, let’s modify index.js
import Axios from './axios';
(async () = > {
let res = await Axios('/data/1.txt', {
headers: {
a: 12.b: '321fho:fdsf vfds; : ',}});console.log(res); }) ();Copy the code
You can see that the result is undefined because we didn’t return our result at all
Modify axios.js at this point
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. Merge header
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. Check whether the parameters are correct
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 merge request
options.url = urlLib.resolve(options.baseUrl, options.url);
delete options.baseUrl;
// 4. Formally call request(options)
returnrequest(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();
letres = { ... JSON.parse(JSON.stringify(_default)) };
merge(res, options);
axios.default = res;
return axios;
};
export default Axios.create();
Copy the code
At this point, we will see if the result is ok. Of course, we can’t just return the original XML object, we will have to do all kinds of processing on the returned data
Process the data
Let’s start with a simple change to the axios.js request
Return another promise, and you can see the result without any problems
But it’s too messy to put all of our stuff in axios.js, so let’s remove it separately
Create two files: / SRC /response.js and/SRC /error.js
Then I’ll introduce axios.js here and hand it to them as I process it
Then return the value directly in response.js
But the headers a little special, it is necessary to separate a method XHR. The getAllResponseHeaders (), but the return is the original XHR head, this is definitely not line, so we need to cut it
~ the 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,
};
}
Copy the code
That will do
transformRequest
&& transformResponse
This is not the end of the story, because we don’t have any data processing yet, so it’s always a string, and the user can customize the processing. Those of you who are familiar with Axios know that Axios has methods transformRequest and transformResponse
Let’s change the request method in axios.js
Now you need to process the request between steps 3 and 4
Print the parameters first, then modify the test demo of index.js
For the convenience of testing I changed 1.txt to 1.json, so that we can deal with JSON data later to see the effect
As you can see, this parameter is available, so it’s a little bit easier to just do it
So if you look at the request, the headers is added
Just for a second, why did I delete it? It doesn’t matter if I don’t delete it, but I want to keep my request clean
As for the custom return result, isn’t it even simpler
And you can look at the result, I didn’t pass in the transformResponse, so it looks like this
That’s it
Of course, we can now use very flexible, not only the single transmission of the JSON parameters can be configured, global configuration unified processing is also possible, let’s try
And between different instances
The interceptor
Interceptors are certainly essential in a request library, and it’s actually quite easy to add one to our library as we write it
~ 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); }) ();Copy the code
Then create a new interceptor.js
~ interceptor. Js
class Interceptor {
constructor() {
this._list = [];
}
use(fn) {
this._list.push(fn);
}
list() {
return this._list; }}export default Interceptor;
Copy the code
~ 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. Merge header
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. Check whether the parameters are correct
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 merge request
options.url = urlLib.resolve(options.baseUrl, options.url);
delete options.baseUrl;
// 4. Change the request
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. Formally call 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 => {
leterr = 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();
Copy the code
As you can see, it’s basically the same thing, just passing in the parameters and calling it
But there are two small problems to deal with
- We’re giving the user a lot of openings now, and if he returns
config
It’s not true or it didn’t return and we should have sent it an error message, and then we’ll check it, and by this point you should have figured it out, I guessaxios
So if you want to check these parameters and make a separate function, it’s not very technical, so I don’t want to talk about it here, but if you’re interested, you can try it out. Okay - We’re giving the user a function, and we’re using theta
forEach
“, which leads to a problem if the user gives you a bandasync
That’s not going to work. Let’s add itasync
andawait
, butasync
andawait
It returns apromise
Again very strange, this everybody is interested can try yourself, or leave a message in the comment section
This is the time to test the effect
As you can see, we’re almost done with the interceptor
conclusion
The final code has been uploaded to Github and linked to github.com/Mikey-9/axi…
Again, this article is not about implementing axios completely, but the idea of implementing such a library. Of course, there are a lot of problems. Please leave your comments in the comments section or add me to qq or wechat
It’s a little long. Thanks for watching
Thank You
qq:
WeChat: