如何实现axios的自定义适配器adapter

作者:小满

时间:2026-10-24

浏览:375

本文详细解析如何在axios中实现自定义适配器(adapter)。内容涵盖适配器的核心功能要求,包括Promise封装、URL与参数拼接、响应格式标准化、超时处理及请求取消机制。通过一个完整的JSONP适配器示例,演示如何编写适配器并将其注册到axios中,支持条件性调用默认或自定义逻辑,帮助开发者灵活扩展axios的请求能力。

文章目录

1. 适配器要实现的功能

在基于 axios 开发额外的数据模块时,应遵循 axios 的设计模式,确保返回的数据格式和功能行为尽可能保持一致。

1.1 promise 和工具

所有适配器必须实现为 Promise 模式。

此外,axios 将部分功能下放给适配器自行实现,例如:

  • URL 拼接:当存在 baseURL 且 url 为相对路径时,需进行拼接;否则直接使用 url。
  • 参数拼接:对于 GET 请求,需将 object 类型的参数转换为 URL 查询字符串格式并拼接到 url 中。

这是适配器需要自行实现的两个基本工具方法。

1.2 响应的格式

需要注意请求接口正常和异常时的格式处理。

接口正常时:


const result = {
    status: 200, // 接口的http 状态
    statusText: 'ok',
    config: 'config', // 传入的config配置,原样返回即可,方便在响应拦截器和响应结果中使用
    data: {}, // 真实的接口返回结果
};

接口异常时,可参考 axios 源码中的错误处理逻辑 createError 和 enhanceError(createError 中调用了 enhanceError)。首先创建 error 实例,然后为该实例添加属性:


module.exports = function enhanceError(error, config, code, request, response) {
    error.config = config;
    if (code) {
        error.code = code;
    }

    error.request = request;
    error.response = response;
    error.isAxiosError = true;

    error.toJSON = function toJSON() {
        return {
            // Standard
            message: this.message,
            name: this.name,
            // Microsoft
            description: this.description,
            number: this.number,
            // Mozilla
            fileName: this.fileName,
            lineNumber: this.lineNumber,
            columnNumber: this.columnNumber,
            stack: this.stack,
            // Axios
            config: this.config,
            code: this.code,
        };
    };
    return error;
};

除了正常的错误信息外,还添加了其他属性,如 request、response、config 等。在实现自定义适配器时,建议统一采用这种格式,以便上层业务层统一处理,避免为特定适配器编写特殊逻辑。

关于 1.1 和 1.2 中的内容,若不进行打包编译,则需要自行实现。若使用 webpack 等打包工具,可直接引用 axios 内部方法,无需自行实现,可参考官方基于 axios 实现的 mock-axios。例如:


import axios from 'axios';
import buildURL from 'axios/lib/helpers/buildURL';
import isURLSameOrigin from 'axios/lib/helpers/isURLSameOrigin';
import btoa from 'axios/lib/helpers/btoa';
import cookies from 'axios/lib/helpers/cookies';
import settle from 'axios/lib/core/settle';
import createError from 'axios/lib/core/createError';

然后直接使用即可,无需二次开发。

1.3 超时设置

不能无限等待第三方服务的响应。如果第三方服务无响应或响应时间过长,应适时终止请求。在 axios 中,前端使用 XMLHttpRequest,Node.js 端使用 http 实现接口请求,两者均支持 timeout 字段设置超时时间,自动取消当前请求。

对于没有内置超时机制的请求,如 JSONP(通过创建 script 标签发起),必须等待服务器响应(成功或失败)才会终止。此时,需自行使用 setTimeout 模拟超时,但即使返回给业务层“超时,已取消”,实际请求仍在进行,只是超过规定时间后不再执行成功操作。

1.4 主动取消请求

许多场景需要在未到达超时时间前主动取消请求,例如在请求返回前切换路由,或在上次请求未响应时发出新请求。这些情况都需要主动取消当前请求。

axios 已提供取消请求功能,只需按规则接入即可。查看 XMLHttpRequest 请求器中如何取消请求,编写自定义请求器时可参考使用。

在 lib/adapters/xhr.js#L158 中:


// 若config中已经配置了cancelToken
if (config.cancelToken) {
    // Handle cancellation
    // 若在外城执行了取消请求的方法,则这里将当前的请求取消掉
    config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
            return;
        }

        // xhr中使用abort方法取消当前请求
        request.abort();
        reject(cancel);
        // Clean up request
        request = null;
    });
}

编写自定义适配器时,可复制此段代码,将内部取消操作替换为自身逻辑即可。

至此,若实现上述功能,即完成了一个标准适配器。

2. 编写自定义适配器

每个人的适配器需求不同,复杂度各异,例如接入小程序请求或客户端提供的数据请求方式。本文通过实现一个简单的 JSONP 适配器讲解实现方式。

以 ES6 模块方式开发,所有实现均在代码中详细讲解。


// 这里的config是axios里所有的配置
const jsonpAdapter = (config) => {
    return new Promise((resolve, reject) => {
        // 是否已取消当前操作
        // 因jsonp没有主动取消请求的方式
        // 这里使用 isAbort 来标识
        let isAbort = false;

        // 定时器标识符
        let timer = null;

        // 执行方法的名字,
        const callbackName = `jsonp${Date.now()}_${Math.random()
            .toString()
            .slice(2)}`;

        // 这里假设已经实现了baseURL和url的拼接方法
        const fullPath = buildFullPath(config.baseURL, config.url);

        // 这里假设已经实现了url和参数的拼接方法
        // 不太一样的地方在于,jsonp需要额外插入一个自己的回调方法
        const url = buildURL(
            fullPath,
            {
                ...config.params,
                ...{ [config.jsonpCallback || 'callback']: callbackName },
            },
            config.paramsSerializer
        );

        // 创建一个script标签
        let script = document.createElement('script');

        // 成功执行操作后
        function remove() {
            if (script) {
                script.onload = script.onerror = null;

                // 移除script标签
                if (script.parentNode) {
                    script.parentNode.removeChild(script);
                }
                // 取消定时器
                if (timer) {
                    clearTimeout(timer);
                }

                script = null;
            }
        }

        // 成功请求后
        window[callbackName] = (data) => {
            // 若已需要请求,则不再执行
            if (isAbort) {
                return;
            }

            // 返回的格式
            const response = {
                status: 200,
                statusText: 'ok',
                config,
                request: script,
                data: data,
            };
            remove();
            // 实际上这里上一个settle操作,会额外判断是否是合理的status状态
            // 若我们在config.validateStatus中设置404是合理的,也会进入到resolve状态
            // 但我们这里就不实现这个了
            // settle(resolve, reject, response);
            resolve(response);
        };

        // 请求失败
        script.onerror = function (error) {
            remove();

            reject(createError('Network Error', config, 404));
        };

        // 若设置了超时时间
        if (config.timeout) {
            timer = setTimeout(function () {
                remove();
                // 取消当前操作
                isAbort = true;
                reject(
                    createError(
                        'timeout of ' + config.timeout + 'ms exceeded',
                        config,
                        405
                    )
                );
            }, config.timeout);
        }

        // 若定义了取消操作
        if (config.cancelToken) {
            config.cancelToken.promise.then(function () {
                if (!script) {
                    return;
                }
                remove();
                isAbort = true;

                reject(createError('Cancel Error', config, 404));
            });
        }

        script.src = url;
        const target =
            document.getElementsByTagName('script')[0] || document.head;
        target.parentNode && target.parentNode.insertBefore(script, target);
    });
};

export default jsonpAdapter;

3. 将适配器添加到 axios 中

axios 的 config 提供 adapter 字段用于插入自定义适配器。使用自定义适配器有两种情况:

1. 完全只使用自定义适配器;

2. 在特定情况下使用自定义适配器,其他情况仍使用 axios 默认适配器。

第 1 种情况只需 return 自定义适配器返回的结果;第 2 种情况有一个小坑,本文仅讲解第 2 种情况。将实现的 JSONP 适配器添加到 axios 中,仅在参数包含 format=jsonp 时调用该适配器,其他情况使用 axios 提供的适配器。


import Axios from 'axios';
import jsonpAdapter from './jsonpAdater';

const request = Axios.create({
    adapter: (config) => {
        if (config?.params?.format === 'jsonp') {
            return jsonpAdapter(config);
        }

        // 这里需要将config.adapter设置为空
        // 否则会造成无限循环
        return defaultAxios({ ...config, ...{ adapter: undefined } });
    },
});

使用自定义适配器 JSONP 发起请求。


// 使用自定义的适配器jsonp发起请求
var options = {
    params: {
        format: 'jsonp',
    },
};
request(
    'https://api.prize.qq.com/v1/newsapp/answer/share/oneQ?qID=506336',
    options
)
    .then(function (response) {
        console.log('jsonp response', response);
    })
    .catch(function (error) {
        console.error('jsonp error', error);
    });

使用 axios 默认适配器发起请求。


// 使用axios默认的适配器发起请求
request('https://api.prize.qq.com/v1/newsapp/answer/share/oneQ?qID=506336')
    .then(function (response) {
        console.log('axios response', response);
    })
    .catch(function (error) {
        console.error('axios error', error);
    });

4. 总结

至此,已实现一个自定义适配器,可在满足特定条件时触发。通过此思路,还可实现自定义 mock 方法,例如当参数包含 format=mock 时调用 mock 接口,否则正常请求。

以上就是如何实现axios的自定义适配器adapter的详细内容,更多关于axios自定义适配器adapter的资料请关注本站其它相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章