无所不能的uniapp拦截器【三】uni-app 拦截器核心流程解析

第一集请见 juejin.cn/post/749270...

第二集请见 juejin.cn/post/754875...

interceptor.js 源码

feat(intercrptors): 修改了queue和wrapperHook的返回值以支持异步resolve(false)) by liujiayii · Pull Request #5819 · dcloudio/uni-app

uni-app 拦截器核心流程解析以及给拦截器添加异步 resolve(false) 终止支持

1. invokeApi 函数详解

invokeApi 是 uni-app 中处理所有 API 调用的核心函数,负责协调拦截器和实际 API 执行。

javascript 复制代码
function invokeApi(method, api, options, ...params) {
    // 1. 获取该 API 的所有拦截器
    const interceptorHooks = getApiInterceptorHooks(method);
    
    // 2. 如果有 invoke 拦截器,调用 queue 函数处理
    if (interceptorHooks && interceptorHooks.invoke) {
        const res = queue(interceptorHooks.invoke, options, params);
        
        // 3. 处理拦截器返回值
        return res.then((options) => {
            // 4. 执行实际 API 调用
            return api(wrapperOptions(interceptorHooks, options), ...params);
        });
    }
    
    // 如果没有拦截器,直接调用 API
    return api(options, ...params);
}

invokeApi 执行流程详解:

  1. 获取拦截器 :通过 getApiInterceptorHooks(method) 获取指定 API 的所有拦截器
  2. 检查 invoke 拦截器 :如果有 invoke 拦截器,调用 queue 函数处理
  3. 处理拦截器返回值
    • 如果拦截器返回 Promise,使用 .then() 处理解析后的值
    • 如果拦截器返回 false,queue 函数会返回空对象,导致后续 API 不执行
  4. 执行实际 API:将处理后的 options 传递给实际 API 函数
  5. 包装选项 :通过 wrapperOptions 函数添加 success/fail/complete 拦截器

2. invoke 拦截器详解

invoke 是拦截器中最关键的钩子函数,在 API 调用前执行,可以修改参数或阻止 API 执行。

typescript 复制代码
// invoke 拦截器的典型实现
uni.addInterceptor('request', {
  invoke(options) {
    // 1. 修改请求参数
    options.url = addBaseUrl(options.url);
    options.header = {
      ...options.header,
      'Authorization': getToken()
    };
    
    // 2. 条件性阻止请求
    if (options.url.includes('blocked-api')) {
      return false; // 同步返回 false 阻止请求
    }
    
    // 3. 异步处理(需要修改 queue 函数支持)
    return new Promise((resolve) => {
      checkPermission().then(hasPermission => {
        if (!hasPermission) {
          resolve(false); // 异步返回 false 阻止请求
        } else {
          resolve(options); // 允许请求继续
        }
      });
    });
    
    // 4. 默认返回修改后的 options
    return options;
  }
});

invoke 拦截器返回值类型:

  1. 返回对象:修改后的参数对象,API 将使用这些参数继续执行
  2. 返回 false:同步阻止 API 执行,queue 函数会返回空对象
  3. 返回 Promise:异步处理,需要修改 queue 函数以支持 resolve(false)
  4. 不返回值:等同于返回 undefined,API 将使用原始参数

3. queue 函数详解

queue 函数是拦截器执行的核心机制,负责按顺序执行多个拦截器并处理返回值。

javascript 复制代码
function queue(hooks, data, params) {
    let promise = false;
    
    // 遍历所有拦截器
    for (let i = 0; i < hooks.length; i++) {
        const hook = hooks[i];
        
        if (promise) {
            // 如果已经有 Promise,将后续 hook 包装成 Promise
            promise = Promise.resolve(wrapperHook(hook, params));
        }
        else {
            // 执行当前拦截器
            const res = hook(data, params);
            
            if (isPromise(res)) {
                // 如果 hook 返回 Promise,将其作为后续处理的基础
                promise = Promise.resolve(res);
                
                // 修改:支持异步返回 false
                promise = res.then(res => {
                    if (res === false) {
                        // 如果异步返回 false,返回空对象
                        return { then() {}, catch() {} };
                    }
                    return res;
                });
            }
            
            if (res === false) {
                // 如果 hook 返回 false,返回空对象阻止后续执行
                return {
                    then() { },
                    catch() { },
                };
            }
        }
    }
    
    // 返回 Promise 或带 then 方法的对象
    return (promise || {
        then(callback) {
            return callback(data);
        },
        catch() { },
    });
}

queue 函数执行流程详解:

  1. 初始化 :设置 promise = false,表示当前没有 Promise 链
  2. 遍历拦截器:按顺序执行每个拦截器
  3. 处理拦截器返回值
    • 同步返回 false :立即返回空对象 {then() {}, catch() {}},阻止后续执行
    • 同步返回其他值:继续执行下一个拦截器
    • 返回 Promise
      • 原始逻辑:将 Promise 作为返回值,解析后继续处理
      • 修改后逻辑:检查 Promise 解析结果,如果是 false 则返回空对象
  4. Promise 链处理:如果已有 Promise,将后续拦截器包装到 Promise 链中
  5. 返回结果
    • 如果有 Promise 链,返回 Promise
    • 否则返回带 then 方法的对象,执行回调并传入 data

4. 三者协作流程

javascript 复制代码
// 1. 用户调用 API
uni.request({
  url: '/api/data',
  success: (res) => console.log(res)
});

// 2. uni-app 内部调用 invokeApi
invokeApi('request', actualRequestFunction, options, ...params);

// 3. invokeApi 获取拦截器并调用 queue
const interceptorHooks = getApiInterceptorHooks('request');
const res = queue(interceptorHooks.invoke, options, params);

// 4. queue 执行所有 invoke 拦截器
// - 如果拦截器返回 false,queue 返回空对象
// - 否则返回处理后的 options

// 5. invokeApi 处理 queue 返回值
return res.then((options) => {
  // 6. 包装 options 添加 success/fail/complete 拦截器
  const wrappedOptions = wrapperOptions(interceptorHooks, options);
  
  // 7. 执行实际 API
  return actualRequestFunction(wrappedOptions, ...params);
});

5. 拦截器终止 API 执行的机制

5.1 同步返回 false 终止机制

当拦截器的 invoke 方法同步返回 false 时:

  1. queue 函数检测到返回值为 false
  2. 立即返回一个空对象 { then() {}, catch() {} }
  3. invokeApi 函数中,调用这个空对象的 then 方法
  4. 由于 then 方法是空的,不会执行回调
  5. 实际的 API 调用在回调中,因此不会执行
javascript 复制代码
// invokeApi 函数中的关键代码
return res.then((options) => {
    // 实际 API 调用在这里
    return api(wrapperOptions(getApiInterceptorHooks(method), options), ...params);
});
5.2 异步返回 false 的问题

当拦截器的 invoke 方法异步返回 false 时:

  1. queue 函数检测到返回值是 Promise
  2. 将 Promise 作为返回值
  3. Promise 解析为 false
  4. invokeApi 函数中,false 被当作 options 参数
  5. 尝试将 false 传递给 wrapperOptions 函数
  6. wrapperOptions 函数尝试给 false 添加属性,导致错误
javascript 复制代码
// wrapperOptions 函数中的关键代码
options[name] = function callbackInterceptor(res) {
    // 这里尝试给 options 添加属性,如果 options 是 false 会报错
    queue(hooks, res, options).then((res) => {
        return (isFunction(oldCallback) && oldCallback(res)) || res;
    });
};

6. 给拦截器添加异步 resolve(false) 终止支持

6.1 修改 queue 函数

要支持异步返回 false,需要修改 queue 函数:详见顶部PR

6.2 使用异步拦截器

修改后,可以在拦截器中异步返回 false

typescript 复制代码
uni.addInterceptor('request', {
  invoke(options) {
    return new Promise((resolve) => {
      // 执行异步检查
      someAsyncCheck().then(shouldBlock => {
        if (shouldBlock) {
          // 异步返回 false 阻止请求
          resolve(false);
        } else {
          // 正常处理请求
          options.url = modifyUrl(options.url);
          resolve(options);
        }
      });
    });
  }
});

7. 其他拦截器核心函数

除了 invokeApiinvokequeue 这三个核心函数外,uni-app 拦截器机制还依赖以下几个重要函数:

7.1 getApiInterceptorHooks 函数

getApiInterceptorHooks 函数负责获取指定 API 的所有拦截器钩子,包括全局拦截器和特定 API 的拦截器。

javascript 复制代码
function getApiInterceptorHooks(method) {
    const interceptor = Object.create(null);
    
    // 1. 复制全局拦截器(除了 returnValue)
    Object.keys(globalInterceptors).forEach((hook) => {
        if (hook !== 'returnValue') {
            interceptor[hook] = globalInterceptors[hook].slice();
        }
    });
    
    // 2. 合并特定 API 的拦截器(除了 returnValue)
    const scopedInterceptor = scopedInterceptors[method];
    if (scopedInterceptor) {
        Object.keys(scopedInterceptor).forEach((hook) => {
            if (hook !== 'returnValue') {
                interceptor[hook] = (interceptor[hook] || []).concat(scopedInterceptor[hook]);
            }
        });
    }
    
    return interceptor;
}

功能说明:

  • globalInterceptors 获取全局拦截器
  • scopedInterceptors[method] 获取特定 API 的拦截器
  • 合并两种拦截器,特定 API 的拦截器优先级更高
  • 不包含 returnValue 拦截器,因为它有单独的处理逻辑
7.2 wrapperOptions 函数

wrapperOptions 函数负责将拦截器的 success/fail/complete 钩子包装到 API 的 options 中。

javascript 复制代码
function wrapperOptions(interceptors, options = {}) {
    // 遍历 success/fail/complete 三种钩子
    [HOOK_SUCCESS, HOOK_FAIL, HOOK_COMPLETE].forEach((name) => {
        const hooks = interceptors[name];
        if (!isArray(hooks)) {
            return;
        }
        
        // 保存原始回调
        const oldCallback = options[name];
        
        // 创建新的回调函数
        options[name] = function callbackInterceptor(res) {
            // 执行所有拦截器钩子
            queue(hooks, res, options).then((res) => {
                // 执行原始回调
                return (isFunction(oldCallback) && oldCallback(res)) || res;
            });
        };
    });
    
    return options;
}

功能说明:

  • 为 success/fail/complete 三种回调创建拦截器链
  • 保留原始回调,确保最终执行
  • 使用 queue 函数按顺序执行所有钩子
  • 将前一个钩子的结果传递给下一个钩子
7.3 wrapperReturnValue 函数

wrapperReturnValue 函数负责处理 API 的返回值,应用 returnValue 拦截器。

javascript 复制代码
function wrapperReturnValue(method, returnValue) {
    const returnValueHooks = [];
    
    // 1. 收集全局 returnValue 拦截器
    if (isArray(globalInterceptors.returnValue)) {
        returnValueHooks.push(...globalInterceptors.returnValue);
    }
    
    // 2. 收集特定 API 的 returnValue 拦截器
    const interceptor = scopedInterceptors[method];
    if (interceptor && isArray(interceptor.returnValue)) {
        returnValueHooks.push(...interceptor.returnValue);
    }
    
    // 3. 按顺序应用所有 returnValue 拦截器
    returnValueHooks.forEach((hook) => {
        returnValue = hook(returnValue) || returnValue;
    });
    
    return returnValue;
}

功能说明:

  • 收集全局和特定 API 的 returnValue 拦截器
  • 按顺序应用所有拦截器
  • 每个拦截器可以修改返回值,也可以返回 undefined 保持原值
  • 前一个拦截器的输出作为下一个拦截器的输入
7.4 addInterceptor 和 removeInterceptor 函数

这两个函数是 uni-app 提供给开发者使用的 API,用于添加和移除拦截器。

javascript 复制代码
// 添加拦截器
const addInterceptor = defineSyncApi(API_ADD_INTERCEPTOR, (method, interceptor) => {
    if (isString(method) && isPlainObject(interceptor)) {
        // 添加特定 API 的拦截器
        mergeInterceptorHook(scopedInterceptors[method] || (scopedInterceptors[method] = {}), interceptor);
    }
    else if (isPlainObject(method)) {
        // 添加全局拦截器
        mergeInterceptorHook(globalInterceptors, method);
    }
}, AddInterceptorProtocol);

// 移除拦截器
const removeInterceptor = defineSyncApi(API_REMOVE_INTERCEPTOR, (method, interceptor) => {
    if (isString(method)) {
        if (isPlainObject(interceptor)) {
            // 移除特定 API 的特定拦截器
            removeInterceptorHook(scopedInterceptors[method], interceptor);
        }
        else {
            // 移除特定 API 的所有拦截器
            delete scopedInterceptors[method];
        }
    }
    else if (isPlainObject(method)) {
        // 移除全局拦截器
        removeInterceptorHook(globalInterceptors, method);
    }
}, RemoveInterceptorProtocol);

功能说明:

  • 支持全局拦截器和特定 API 拦截器
  • 使用 mergeInterceptorHookremoveInterceptorHook 管理拦截器
  • 通过 defineSyncApi 定义为同步 API
7.5 mergeInterceptorHook 和 removeInterceptorHook 函数

这两个辅助函数用于合并和移除拦截器钩子。

javascript 复制代码
// 合并拦截器钩子
function mergeInterceptorHook(interceptors, interceptor) {
    Object.keys(interceptor).forEach((hook) => {
        if (isFunction(interceptor[hook])) {
            interceptors[hook] = mergeHook(interceptors[hook], interceptor[hook]);
        }
    });
}

// 移除拦截器钩子
function removeInterceptorHook(interceptors, interceptor) {
    if (!interceptors || !interceptor) {
        return;
    }
    Object.keys(interceptor).forEach((name) => {
        const hooks = interceptors[name];
        const hook = interceptor[name];
        if (isArray(hooks) && isFunction(hook)) {
            remove(hooks, hook);
        }
    });
}

// 合并钩子函数
function mergeHook(parentVal, childVal) {
    const res = childVal
        ? parentVal
            ? parentVal.concat(childVal)
            : isArray(childVal)
                ? childVal
                : [childVal]
        : parentVal;
    return res ? dedupeHooks(res) : res;
}

// 去重钩子函数
function dedupeHooks(hooks) {
    const res = [];
    for (let i = 0; i < hooks.length; i++) {
        if (res.indexOf(hooks[i]) === -1) {
            res.push(hooks[i]);
        }
    }
    return res;
}

功能说明:

  • mergeInterceptorHook 将新拦截器合并到现有拦截器集合中
  • removeInterceptorHook 从拦截器集合中移除特定拦截器
  • mergeHook 处理钩子函数的合并逻辑
  • dedupeHooks 确保同一个钩子函数不会被重复添加
7.6 promisify 函数

promisify 函数负责将回调风格的 API 转换为 Promise 风格,同时应用拦截器。

javascript 复制代码
function promisify(name, fn) {
    return (args = {}, ...rest) => {
        if (hasCallback(args)) {
            // 如果有回调,直接调用 invokeApi
            return wrapperReturnValue(name, invokeApi(name, fn, extend({}, args), rest));
        }
        // 如果没有回调,创建 Promise 并调用 invokeApi
        return wrapperReturnValue(name, handlePromise(new Promise((resolve, reject) => {
            invokeApi(name, fn, extend({}, args, { success: resolve, fail: reject }), rest);
        })));
    };
}

功能说明:

  • 检测是否提供了回调函数
  • 如果有回调,使用回调风格调用 API
  • 如果没有回调,使用 Promise 风格调用 API
  • 无论哪种风格,都应用 wrapperReturnValue 处理返回值
7.7 拦截器存储结构

uni-app 使用两个全局对象存储拦截器:

javascript 复制代码
// 全局拦截器,对所有 API 生效
const globalInterceptors = {};

// 特定 API 的拦截器,只对指定 API 生效
const scopedInterceptors = {};

拦截器类型:

  • invoke:API 调用前拦截
  • success:API 成功回调拦截
  • fail:API 失败回调拦截
  • complete:API 完成回调拦截
  • returnValue:API 返回值拦截

8. 总结

uni-app 拦截器机制由多个函数协同工作,除了核心的 invokeApiinvokequeue 函数外,还包括:

  1. getApiInterceptorHooks:获取 API 的所有拦截器
  2. wrapperOptions:包装 API 选项,添加回调拦截器
  3. wrapperReturnValue:处理 API 返回值,应用返回值拦截器
  4. addInterceptor/removeInterceptor:提供给开发者使用的 API
  5. mergeInterceptorHook/removeInterceptorHook:管理拦截器的辅助函数
  6. promisify:将回调风格 API 转换为 Promise 风格
  7. globalInterceptors/scopedInterceptors:存储拦截器的全局对象

这些函数共同构成了 uni-app 强大的拦截器系统,支持在 API 调用的各个阶段进行拦截和处理,为开发者提供了灵活的扩展能力。通过理解这些函数的工作原理,我们可以更好地利用拦截器机制,实现复杂的功能需求。

相关推荐
云枫晖2 小时前
破壁前行:深度解析前端跨域的本质与实战
前端·浏览器
文心快码BaiduComate2 小时前
代码·创想·未来——百度文心快码创意探索Meetup来啦
前端·后端·程序员
小白64023 小时前
前端梳理体系从常问问题去完善-框架篇(Vue2&Vue3)
前端
云和数据.ChenGuang3 小时前
vue中构建脚手架
前端·javascript·vue.js
渣哥3 小时前
面试官最爱刁难:Spring 框架里到底用了多少经典设计模式?
javascript·后端·面试
尘似鹤3 小时前
微信小程序学习(六)--多媒体操作
学习·微信小程序·小程序
千与千寻酱3 小时前
排列与组合在编程中的实现:从数学概念到代码实践
前端·python
朱昆鹏3 小时前
如何通过sessionKey 登录 Claude
前端·javascript·人工智能
wdfk_prog3 小时前
klist 迭代器初始化:klist_iter_init_node 与 klist_iter_init
java·前端·javascript