【axios源码】- 请求分发函数DispatchRequest研读解析

470 阅读4分钟

「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战

首发于我的公众号「前端面壁者」,欢迎关注。

一、环境准备

  • axios 版本 v0.24.0

  • 通过 github1s 网页可以 查看 axios - dispatchRequest.js 源码

  • 调试需要 clone 到本地

git clone https://github.com/axios/axios.git

cd axios

npm start

http://localhost:3000/

二、函数研读

1. 辅助函数总览

requestDispatch 引用了取消请求Cancel模块、实例化配置模块defaults、工具函数utils与日期转换函数transformData,其中前三个部分已经在前文中分析过了,这里不再赘述。

"use strict";

var utils = require("./../utils");
var transformData = require("./transformData");
var isCancel = require("../cancel/isCancel");
var defaults = require("../defaults");
var Cancel = require("../cancel/Cancel");

2. 辅助函数 transformData

Transform the data for a request or a response 将请求或返回结果中的 Data

"use strict";

var utils = require("./../utils");
var defaults = require("./../defaults");

/**
 * @param {Object|String} data The data to be transformed
 * @param {Array} headers The headers for the request or response
 * @param {Array|Function} fns A single function or Array of functions
 * @returns {*} The resulting transformed data
 */
module.exports = function transformData(data, headers, fns) {
    var context = this || defaults;
    /*eslint no-param-reassign:0*/
    utils.forEach(fns, function transform(fn) {
        data = fn.call(context, data, headers);
    });

    return data;
};

===》调用地点
config.data = transformData.call(
    config,
    config.data,
    config.headers,
    config.transformRequest
);

===》config.transformRequest 配置地点 default.js
 transformResponse: [
    function transformResponse(data) {
        ...
        if (
            strictJSONParsing ||
            (forcedJSONParsing && utils.isString(data) && data.length)
        ) {
            try {
                return JSON.parse(data);
            } catch (e) {
                ...
            }
        }
        return data;
    },
],
  • 声明全局变量context从当前上下文this或者实例defaults获取全局上下文
  • 调用utils中的forEachfns中的每一个函数fn都会被回调触发执行其内部逻辑,每个fn的入参都是dataheaders
  • 从上下文看其实就是用JSON.parse()处理了一下dataheaders就没使用到

Tips:这一块需要结合上下文一起看,否则从命名和功能分析上看知道是个处理日期的工具函数,但是具体的处理逻辑是什么?执行到哪里才会处理?就不清楚了,这也是读源码比较费劲的地方,所以一定要联系上下文。

3. 内部函数 throwIfCancellationRequested

Throws a Cancel if cancellation has been requested

如果取消请求的行为已经执行,则抛出一个 Cancel 对象

function throwIfCancellationRequested(config) {
    if (config.cancelToken) {
        config.cancelToken.throwIfRequested();
    }

    if (config.signal && config.signal.aborted) {
        throw new Cancel("canceled");
    }
}
  • 第一个判断cancelToken是否被实例化,如果是就执行实例的throwIfRequested方法,通过上一篇文章我们知道cancelToken就是一个发布订阅函数,一旦被实例化会立即执行内部的订阅者事件cancel,如果是首次取消会通过resolvePromise返回一个 Cancel实例
  • 第二个判断是针对fetch API做的支持,这一点还没有在文档中体现,但可以在axios-README-line740看到使用示例

4. 分发函数 dispatchRequest

Dispatch a request to the server using the configured adapter

用已经配置好的适配器向服务器发送请求

/**
 * @param {object} config The config that is to be used for the request
 * @returns {Promise} The Promise to be fulfilled
 */
module.exports = function dispatchRequest(config) {
    throwIfCancellationRequested(config);

    // Ensure headers exist
    config.headers = config.headers || {};

    // Transform request data
    config.data = transformData.call(
        config,
        config.data,
        config.headers,
        config.transformRequest
    );

    // Flatten headers
    config.headers = utils.merge(
        config.headers.common || {},
        config.headers[config.method] || {},
        config.headers
    );

    utils.forEach(
        ["delete", "get", "head", "post", "put", "patch", "common"],
        function cleanHeaderConfig(method) {
            delete config.headers[method];
        }
    );

    var adapter = config.adapter || defaults.adapter;

    return adapter(config).then(
        function onAdapterResolution(response) {
            throwIfCancellationRequested(config);

            // Transform response data
            response.data = transformData.call(
                config,
                response.data,
                response.headers,
                config.transformResponse
            );

            return response;
        },
        function onAdapterRejection(reason) {
            if (!isCancel(reason)) {
                throwIfCancellationRequested(config);

                // Transform response data
                if (reason && reason.response) {
                    reason.response.data = transformData.call(
                        config,
                        reason.response.data,
                        reason.response.headers,
                        config.transformResponse
                    );
                }
            }

            return Promise.reject(reason);
        }
    );
};
  • 调用throwIfCancellationRequested,如果用户主动取消请求会直接 resolve 或者 reject,不再执行后续逻辑

  • 调用transformData转换请求头中的日期格式,.call传递当前上下文config

  • 调用utils中的merge函数将headers对象信息便平化处理(可以用 ES6 中数组flat方法理解)

  • 调用utils中的forEach函数,每次遍历给定数组元素都会触发cleanHeaderConfig方法,该方法会删除多余的被合并过的headers配置

  • 定义全局adapter接受当前实例的适配器

  • 返回适配器执行结果,由于适配器内发送异步请求,需要在链式调用.then()中拿到返回值,并且在这个过程中需要对 resolvedreject值做一些处理,处理逻辑分别在onAdapterResolutiononAdapterRejection

  • onAdapterResolutiononAdapterRejection中均调用了throwIfCancellationRequested,用于检测用户是否在此时触发了取消请求的动作。那么有问题了-请求都已经发过了,甚至结果都返回了还再做这些干啥呢?答,为了不再执行后续的逻辑,可以提升性能。

Tips:关于数组的扁平化可以参考阮一峰老师的-es6 入门

Tips: 不用过多的纠结适配器的概念,后面的文章会深入分析[Browser、node]的适配器

三、总结

  请求分发模块的逻辑相对是比较简单的,复杂的地方可能就在throwIfCancellationRequested的调用以及承接上下文上了。throwIfCancellationRequested个人理解就是用户主动取消请求时的出口之一,这个出口的执行时间点是请求已经发送还未响应或者已经响应时。对比之前的cancel模块,那里的出口是在执行器executor内,发生在客户端的ajax请求以及nodehttp建立前或者是请求结束后的重复请求时,执行时间上会早于或者晚于当前DispatchRequest内的这个出口。

四、参考

1. 仙凌阁的文章详细 Axios 源码解读

2. 林景宜的文章林景宜的记事本 - Axios 源码解析(五):核心工具方法

3. 若川的文章学习 axios 源码整体架构,打造属于自己的请求库

4. 杰凌的文章深入解读 axios 源码