axios源码分析|手撕源码|理解默认导出实例和create的实例

125 阅读2分钟
/**
* Axios构造函数
* @param config
* @constructor
* config/interceptors
*/
function Axios(config) {
    // 每个axios实例拥有实例属性config对象
    this.config = config
    // 每个axios实例拥有实例属性interceptors对象: 请求拦截器和相应拦截器
    this.interceptors = {
        request: new InterceptorManager(),
        response: new InterceptorManager()
    }
}

/**
* axios请求实例方法
* @param {*} config
* @returns
*/
Axios.prototype.request = function (config) {
    // 初始化一个fulfilled状态promise 用于从执行then回掉开始
    let promise = Promise.resolve(config)
    // 请求拦截器+分派请求+响应拦截器的执行链
    // 每次弹出两个作为promise.then的成功和失败回掉
    const chains = [dispatchRequest, undefined]
    // 遍历添加请求拦截器到执行链
    this.interceptors.request.handlers.forEach(item => {
        chains.unshift(item.fulfilled, item.rejected)
    })
    // 遍历添加响应拦截器到执行链
    this.interceptors.response.handlers.forEach(item => {
        chains.push(item.fulfilled, item.rejected)
    })
    // 遍历执行链 每次取出两个
    while(chains.length) {
        // 通过promise的then函数链 返回最终的promise结果
        // fulfilled就一直调用成功回掉 出现rejected就一直调用失败回掉
        promise = promise.then(chains.shift(), chains.shift())
    }
    return promise
}

/**
* get实例方法
* @param {*} config
*/
Axios.prototype.get = function(config) {
    this.request(config)
}

/**
* post实例方法
* @param {*} config
*/
Axios.prototype.post = function(config) {
    this.request(config)
}

/**
* 根据运行环境选择发送网络请求 浏览器ajax node为http
* @param {*} config
* @returns
*/
function dispatchRequest(config) {
    return xhrAdapter(config)
}

/**
* 通过ajax发送网络请求
* @param {*} config
* @returns
*/
function xhrAdapter(config) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()
        xhr.open(config.method, config.url)
        xhr.send(config.data)
        xhr.onreadystatechange = () => {
            if(xhr.readyState === 4) {
                if(xhr.status >= 200 && xhr.status < 300) {
                    resolve(
                            {
                                //配置对象
                                config: config,
                                //响应体
                                data: JSON.parse(xhr.response),
                                //响应头
                                headers: xhr.getAllResponseHeaders(), //字符串 parseHeaders
                                // xhr 请求对象
                                request: xhr,
                                //响应状态码
                                status: xhr.status,
                                //响应状态字符串
                                statusText: xhr.statusText
                            }
                       )
                }else{
                    reject(new Error('请求失败 失败的状态码为' + xhr.status))
                }
            }
        }
        // 设置取消请求配置
        if(config.cancelToken) {
            config.cancelToken.promise.then(v => xhr.abort())
        }
    })
}

/**
* 拦截器管理对象
* @constructor
*/
function InterceptorManager() {
// 一个管理对象负责维护一个拦截器数组 每个拦截器有成功回掉和失败回掉
    this.handlers = []
}

/**
* 拦截器管理对象添加拦截器
* @param {*} fulfilled
* @param {*} rejected
*/
InterceptorManager.prototype.use = function(fulfilled, rejected) {
    this.handlers.push({
        fulfilled,
        rejected
    })
}

/**
* 创建axios函数对象
* 默认导出的axios实例 是request函数对象绑定了this为request对应的实例axios
* 将request对应的axios实例属性赋值到默认导出axios实例
* @param config
* @returns {any}
*/
function createInstance(config) {
    const context = new Axios(config)
    const instance = Axios.prototype.request.bind(context)
    // 遍历当前对象和原型连上的属性
    for(const key in context) instance[key] = context[key]
    return instance
}
const axios = createInstance({method: 'GET'})
axios.create = createInstance

/**
* 请求取消
* 内部暴露出该CancelToken的属性promise的 reslove方法 给执行器回掉函数
* 传入的回掉函数接受到该resolve函数即可以自己控制该CancelToken的promise的状态
* 如果状态为fulfilled 则axios在发送请求阶段会自动调用CancelToken的promise的then回掉函数
* 然后该回掉函数执行xhr.abort()取消请求发送
* @param executor
* @constructor
*/
function CancelToken(executor) {
    let resolvePromise
    this.promise = new Promise(resolve => {
        resolvePromise = resolve
    })
    function cancel() {
        resolvePromise()
    }
    executor(cancel)
}

export {axios, CancelToken}