自己动手实现一个axios

更新日期: 2019-09-06阅读: 2.2k标签: axios

前言

作为一名前端er,对于数据请求的第三方工具axios,一定不会陌生,如果还是有没有用过,或者不了解的小伙伴,这里给你们准备了贴心的中文文档 ,聪明的你们一看就会~唔,为了更好的了解和学习 axios 封装思想和实现原理,我们一起来动手来实现一个简版的 axios ~


前期准备

工欲善其事,必先利其器,我们在开始我们的项目之前,一定要做好其相关的准备工作,我们需要准备的也很简单,一个 客户端(client) 方便我们调试,一个 服务端(server) 做接口测试~

服务端

服务端我这里为了方便调试,直接使用基于 nodejs 实现的 koa 框架,通过 koa-router 来实现接口,参考代码如下:

const Koa = require('koa');
const KoaRouter = require('koa-router')

//app 实例
const app = new Koa();
//router 实例
const router = new KoaRouter();

//请求中间件,解决跨域
app.use(async (ctx,next)=>{
    ctx.set('Access-Control-Allow-Origin', '*');
    ctx.set('Access-Control-Allow-Headers', 'content-type,token,accept');
    ctx.set('Access-Control-Allow-Methods', 'POST,GET,OPTIONS');
    ctx.set("Content-Type", "application/json")
    ctx.set('Access-Control-Max-Age', 10)
    //处理 options
    if (ctx.request.method.toLowerCase() === 'options'){
        ctx.response.status = 200;
        ctx.body = '';
    } else await next();
})

//接口测试地址
router.get('/',async  ctx=>{
    ctx.body = {
        data : 'Hello World'
    }
})

router.get('/user/info',async ctx =>{
    ctx.body = {
        name : 'Chris' ,
        msg : 'Hello World'
    }
})

app.use(router.routes());

//启动服务
app.listen(3000,function () {
    console.log('app is running ~')
})

这里我们通过 node app.js 就可以启动我们的服务,如果你在服务端控制台看到 app is running ~说明你的服务已经启动成功,此时你打开浏览器访问 http://localhost:3000/ ,不出意外你能看到 Hello World 的返回信息,说明服务端这一块就 配置 ok 了,是不是 so easy~

客户端

客户端这块的话,emm,我们需要准备一个 html 文件,和 一个 js 文件夹,主要存放我们要实现的核心代码~

html 文件非常简单,如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <title>axios-demo</title>
</head>
<body>
    <div class="">
        <h1>axios 的简版实现</h1>
    </div>
    <script src="./js/main.js"></script>
</body>
</html>

其中 main.js 是我们的要使用的js文件~

要注意的是,由于我们的代码是基于 es6 模块化开发的,如果直接丢到浏览器里,是无法识别的,会报错,不过也没关系,我们可以借助第三方的打包工具帮我们搞定这些事~

打包不是我们主要关注的问题,这里我就不采用webpack这种工具,给大家推荐一个零配置的打包工具 Parcel ,使用方式也很简单,在你的客户端目录下通过 npm init -y 初始化,通过 npm install parcel-bundler --save-dev 安装 Parcel ,然后在你的 package.json 文件中添加如下脚本:

 "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "parcel ./*.html",
    "build": "parcel build ./*.html"
  },

这样,我们可以通过 npm run dev 脚本打开我们的 html 文件,如果你们跟我们配置一样,那么你在浏览器的 http://localhost:1234/ 地址会看到 axios 的简版实现 这几个字,并且控制台不会报错,就证明一切准备 ok 了!!

具体实现

雏形

我们首先在客户端的 js 文件夹下创建一个 axios 的文件夹,里面存放我们自己实现的 axios 相关代码。

在 axios 文件夹下新建 index.js 入口文件 和 axios.js 核心js文件~

axios的本质是一个类,这里我们通过 class 实现,即:

axios.js

class Axios {
    constructor(){
    
    }
}
export default Axios;

通过 index.js 进行 new 初始化,导出 axios 实例,这也是我们在使用axios中 不需要 new 的原因~

index.js

import Axios from './Axios'

const axios = new Axios();

export default axios;

此时,我们只需要在 main.js 通过 import 导入即可

main.js

import axios from './axios'

console.log(axios)

此时整个 axios 雏形就已经完成了~

一个简单的get请求

我们先实现一个简单 axios.get 方法,即通过 axios.get 获取我们服务端的响应~

我们回忆一下我们平时使用 axios.get 的时候,通常是 axios.get().then 的方式,那么我们首先就确定了我们的 axios.get 方法返回的是一个 Promise 对象,我们在 axios.js 中添加这个方法~

    get(url){
        return new Promise((resolve => {
            let xhr = new XMLHttpRequest();
            
            xhr.onload = function() {
                resolve({
                    data: JSON.parse(xhr.responseText),
                    status: xhr.status,
                    statusText: xhr.statusText
                });
            }
            
            xhr.open( 'get', url , true );
            xhr.send();
        }))
    }

此时我们在 main.js 调用 get 方法 ,

axios.get('http://127.0.0.1:3000/user/info').then(res=>{
    console.log(res);
})

控制台输出如下:


对比官方的 axios,我们少了比如 header 之类的信息,因为官方对请求返回做了二次包装,这里我们只是简单的json处理,具体的要根据返回的数据类型做不同的处理~

默认配置

我们在使用官方 axios 的,会有很多配置项,包括全局配置,实例配置和请求配置,因此我们就来看看配置信息这一块。

我们在 axios 文件夹下新建一个 config.js ,用于 axios 的默认配置,为了方便,我们的默认配置如下:

config.js

export default {
    baseURL : '' ,
    method : 'get' ,
    headers : {
        'content-type' : 'application/json'
    }
}

我们将默认的配置传入到我们的构造函数中,如下:

index.js

import Axios from './Axios'
import config from './config'

const axios = new Axios(config);

export default axios;

所以,我们需要在构造函数中接收一个 config 参数进行处理,即将默认配置写入到实例中,即:

axios.js

constructor(config){
    //配置
    this.defaults = config;
}

这样我们的 get 方法里请求的 url 就可以改写成 :

this.defaults.baseURL += url
......
xhr.open( 'get', this.defaults.baseURL , true );
//添加header头
for(let key in configs.headers){
    xhr.setRequestHeader(key,configs.headers[key])
}
......

如果你此时在config.js 中配置 baseURL 那么,你在axios.get中就可以省略前面的 baseURL , 因为在请求之前已经帮你拼接完成了~

当然,你也可以通过 axios.defaults.baseURL = xxx这种方式修改默认配置,都是没问题的~

实例配置

在使用官方 axios 的时候,我们可以通过一个create 方法创建一个axios实例,并传入配置信息即可,我们只需要在 index.js 中创建的 axios 添加一个 create 方法即可 。

index.js

axios.create = function (config) {
    return new Axios(config);
}

这样我们也可以通过 create 方法构建一个 axios 实例,它也拥有相应的方法~

但是这么做存在一个问题,如果我们创建多个实例,传入不同的 config ,由于我们直接在构建的时候 通过 this.defaults = config; 这种方式复制,并没有切断对象的引用关系,因此会导致配置对象会被相互引用,出问题~

因此,我们需要对其进行 深拷贝 赋值,即 this.defaults = deepClone(config) , 其中 deepClone 时深拷贝函数,这里不再赘述~

请求配置

我们发现官方的 axios 的get、post等请求会有第二个可选参数,也是 config ,即单独本次请求的配置,如果存在,我们需要进行配置合并,对于简单的 baseURL、method 等这种简单的配置直接覆盖,对于headers这种复杂的对象配置,进行对象合并,有点类似 Object.assign 方法~

所以,我们更改我们的 get 方法如下:

get(url,config){

    let configs = mergeConfig(this.defaults,config);

    return new Promise((resolve => {
        let xhr = new XMLHttpRequest();
    
        xhr.onload = function() {
            resolve({
                data: JSON.parse(xhr.responseText),
                status: xhr.status,
                statusText: xhr.statusText
            });
        }
    
        xhr.open( 'get', configs.baseURL + url , true );
        //添加header头
        for(let key in configs.headers){
            xhr.setRequestHeader(key,configs.headers[key])
        }
        xhr.send();
    }))
}

其中 mergeConfig 是合并两配置对象的方法,具体实现参考如下:

function mergeConfig (obj1, obj2) {
    let target = deepClone(obj1),
        source = deepClone(obj2);
    
    return Object.keys(source).reduce((t,k)=>{
        if(['url','baseURL','method'].includes(k)){
            t[k] = source[k]
        }
        if(['headers'].includes(k)){
            t[k] = Object.assign({},source[k],t[k])
        }
        return t;
    },target)
}

ok~ 现在我们就可以通过如下方式进行请求了:

axios.get('/user/info',{
    baseURL : 'http://127.0.0.1:3000' ,
    headers : {
        token : 'x-token-123456'
    }
}).then(res=>{
    console.log(res);
})

可以看到控制台输出跟之前的是一样的~

细心的小伙伴可以看到 header 头已经添加了 token 信息~

拦截器

拦截器主要用于在请求之前或者请求之后可自定义对配置或者响应结果做一系列的处理,axios官方给我们提供了 use 方法,可以添加多个拦截器,使用方式如下:

// Add a request interceptor
axios.interceptors.request.use(function (config) {
        // Do something before request is sent
        return config;
    }, function (error) {
        // Do something with request error
        return Promise.reject(error);
    });
 
// Add a response interceptor
axios.interceptors.response.use(function (response) {
        // Do something with response data
        return response;
    }, function (error) {
        // Do something with response error
        return Promise.reject(error);
    });

那么,接下来我们自己来实现这么一个 use 方法~

首先我们需要在我们的 axios 实例上添加一个 interceptors 对象,该对象有 request 和 response两个属性,他们都拥有 use 方法,我们发现 use 方法的结构都相同,入参为两个函数,其实他们是同一个 Interceptor 类的不同实例而已。

我们先来构建 Interceptor 这个类,首先在 axios 文件夹下新建 Interceptor.js 文件,并定义如下:

Interceptor.js

export default class Interceptor {
    
    constructor() {
        this.handlers = [];
    }
    
    use( resolvedHandler, rejectedHandler ) {
        this.handlers.push({
            resolvedHandler,
            rejectedHandler
        });
    }
}

这里,我们 new 出来的的实例都会拥有 use 方法,并且我们通过一个 handlers 数组来保存,这样可以保证我们可以多调用 use 方法,添加多个拦截器~

我们只需在 Axios.js 中的 constructor 构造函数中初始化即可。

Axios.js

constructor(config){
    //默认配置
    this.defaults = deepClone(config);
    //拦截器
    this.interceptors = {
        request : new Interceptor() ,
        response : new Interceptor()
    }
}

这样尽管我们已经可以在我们的 main.js 中使用 use 方法添加拦截器了,但是还是无法正确使用,因为请求这一块还未进行处理,接下来,我们需要对我们之前的 Axios.js 进行改造~

首先,我们统一封装一个 request 函数,往后所有的请求都会调用这个方法,入参需要一个 config,返回一个 Promise 对象,我们在这里对拦截器进行操作,定义如下:

//request请求
request (config) {
    //配置合并
    let configs = mergeConfig(this.defaults, config);
    //将配置转成 Promise 对象,链式调用和返回 Promise 对象
    let promise = Promise.resolve(configs);
    
    //请求拦截器,遍历 interceptors.request 里的处理函数
    let requestHandlers = this.interceptors.request.handlers;
    requestHandlers.forEach(handler => {
        promise = promise.then(handler.resolvedHandler, handler.rejectedHandler)
    });
    
    //数据请求
    promise = promise.then(this.send)
    
    //相应拦截器,遍历 interceptors.response 里的处理函数
    let responseHandlers = this.interceptors.response.handlers;
    responseHandlers.forEach(handler => {
        promise = promise.then(handler.resolvedHandler, handler.rejectedHandler)
    })
    
    //返回响应信息
    return promise;
}

上面,为了代码简洁,我又将 send 方法提出来,定义跟之前基本一致:

//发送请求
send (configs) {
    return new Promise((resolve => {
        let xhr = new XMLHttpRequest();
    
        xhr.onload = function () {
            resolve({
                data: JSON.parse(xhr.responseText),
                status: xhr.status,
                statusText: xhr.statusText
            });
        }
        xhr.open(configs.method, configs.baseURL + configs.url, true);
    
        //添加header头
        for ( let key in configs.headers ) {
            xhr.setRequestHeader(key, configs.headers[key])
        }
    
        xhr.send();
    }))
}

哦对啦,我们之前的 get 方法也有一点点的不同,主要是加入了请求拦截器~

// 发送get请求
get (url, config) {
    config.method = 'get';
    config.url = url;
    return this.request(config);
}

趁热打铁,我们来试试~

这里我在 main.js 中分别添加了 2 个响应拦截器和请求拦截器:

//请求拦截器
axios.interceptors.request.use(config=>{
    console.log('请求配置信息:',config);
    return config
})

axios.interceptors.request.use(config=>{
    config.headers.token = 'x-token-654321';
    return config
})

//响应拦截器
axios.interceptors.response.use(res=>{
    console.log('请求响应信息',res)
    return res;
})

axios.interceptors.response.use(res=>{
    res.msg = 'request is ok ~';
    return res;
})

请求拦截器分别打印了请求的配置并将请求的 token 值经行了修改,响应拦截器分别打印了响应信息并将响应添加了 msg 的属性~

不出意外,你在控制台可以看到如下信息,在请求 header 里看到 token 已经被更改~


大功告成!总算是有点样子啦~


结语

至此,我们自己封装了一个非常简单的 axios 的请求库,由于篇幅有限,这里我只是用了最简单的 get 请求示例,axios源码中远不止这些,像一些异常处理、取消请求等的一系列的东西都还没有实现,这里主要是借鉴其一些思想和实现的思路,我这里只是牵个头,剩下的靠你们自己不断的去完善,动动手总是好的~


链接: https://www.fly63.com/article/detial/5712

axios处理Http请求的基本使用方法总汇

axios的功能特性:在浏览器中发送 XMLHttpRequests 请求,在 node.js 中发送 http请求,支持 Promise API,拦截请求和响应,转换请求和响应数据,自动转换 JSON 数据,客户端支持保护安全免受 XSRF 攻击

axios的特点与使用_解决处理axios兼容性问题

axios基于 Promise 的 HTTP 请求客户端,可同时在浏览器和 node.js 中使用。项目中发现,在安卓4.3及以下的手机不支持axios的使用,主要就是无法使用promise。加上以下polyfill就可以了。

axios常见传参方式_axios中get/post/put/patch请求

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。axios中get/post/put/patch请求。传参格式为 formData ,传参格式为 query 形式 ,传参格式为 raw等

axios-mock-adapter_一个axios调试好用的工具

axios-mock-adapter可以用来拦截http请求,并模拟响应,使用起来也很简单,比如你想模拟下服务器返回个500错误,什么404找不到、403禁止访问、500服务器错误、503服务不可用、504网关超时等等,你都能模拟出来

vue中axios的使用与封装

分享下我自己的axios封装,axios是个很好用的插件,都是一些params对象,所以很方便做一些统一处理。当然首先是npm安装axios 很简单。在src下新建文件夹 service / index.js,接着上代码

vue axios不缓存get请求(防止返回304不更新数据)

最近做项目遇到一款浏览器,由于缓存了get请求,导致不管如何刷新,数据都不更新的问题。以下分享一下解决办法:解决思路就是给每一条get请求增加一个timestamp的参数,value为时间戳

vue中axios请求的封装

发送请求模块目录,@/api/url中存放的是每个模块的URL,使用webpack提供的require.context将src/api/url下后缀为js的所有文件引入,并整理出一个对象。整合common.js & product.js,最终得到的对象如下:

axios基于常见业务场景的二次封装

axios的二次封装,功能实现:1.兼容ie浏览器避免缓存2.减少或更新重复请求3.接口域名使用环境变量4.全局loading状态5.可关闭的全局错误提醒6.可开启携带全局分页参数

Vue+Typescript中在Vue上挂载axios使用时报错

在vue项目开发过程中,为了方便在各个组件中调用axios,我们通常会在入口文件将axios挂载到vue原型身上,如下:这样的话,我们在各个组件中进行请求时

vue axios 拦截器

项目中需要验证登录用户身份是否过期,是否有权限进行操作,所以需要根据后台返回不同的状态码进行判断。axios的拦截器分为请求拦截器和响应拦截器两种。我一般把拦截器写在main.js里。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!