直接 new Proxy 无法自动处理远程调用,因其仅拦截属性访问,而 RPC 需序列化、网络请求、响应解析等逻辑,须在 handler.get 中链式返回带路径的 Proxy、在 handler.apply 中发起真实请求。

为什么直接 new Proxy 无法自动处理远程调用
Proxy 只能拦截对象属性访问,但 RPC 调用需要序列化方法名、参数、处理网络请求、等待响应并反序列化结果——这些逻辑 Proxy 本身不提供。你必须在 handler.apply 或 handler.get 中手动注入网络层,否则得到的只是一个空壳对象,调用时会静默失败或抛出 TypeError: xxx is not a function。
常见错误是只代理了顶层对象,没处理嵌套路径(如 api.user.profile.get()),导致深层方法访问时触发 get 返回新 Proxy,但没透传上下文,最终调用无目标。
- 必须在
get中持续返回新的 Proxy,保持路径可链式访问 - 真正发起请求只能发生在函数被调用时,即
apply阶段 - 需缓存已生成的远程方法代理,避免重复创建相同路径的 Proxy
如何用 handler.get + handler.apply 构建可链式调用的 RPC 代理
核心思路:每次 get 都返回一个携带当前路径的 Proxy;当该 Proxy 被当作函数调用时,apply 拿到完整路径(如 ["user", "profile", "update"])和参数,再发请求。
示例中用 targetPath 作为私有字段记录路径,避免污染原始对象:
const createRPCProxy = (baseUrl) => {
const cache = new Map();
const handler = {
get(target, prop) {
if (prop === 'then') return undefined; // 防止被误判为 Promise
const path = [...target.path, prop];
const key = path.join('.');
if (!cache.has(key)) {
cache.set(key, new Proxy({ path }, handler));
}
return cache.get(key);
},
apply(target, thisArg, args) {
const methodPath = target.path.join('.');
return fetch(`${baseUrl}/${methodPath}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(args)
}).then(r => r.json());
}
};
return new Proxy({ path: [] }, handler);
};
这样 rpc.user.list(10) 实际请求的是 POST /user.list,参数 [10] 作为请求体发送。
按需加载的关键:延迟初始化 + 方法级缓存控制
“按需”不是指懒加载 JS 文件,而是指只在方法首次被调用时才建立对应端点的元信息(比如校验签名、获取 schema、预热连接)。如果你的后端支持 GET /methods 返回可用方法列表,可以在 apply 首次执行前拉取并缓存。
- 用
WeakMap存储每个方法路径对应的初始化状态,避免内存泄漏 - 对高频调用方法(如
health.check)可跳过初始化,直接发请求 - 若后端返回 404 或 501,应清除对应 cache 条目,下次调用重新尝试
注意:不要在 get 阶段发请求——这会导致属性访问就触发网络 IO,破坏同步语义,且无法传递调用参数。
容易忽略的边界:Promise 链、this 绑定与错误透明性
用户期望 rpc.user.get(123).then(...) 能正常工作,但 Proxy 返回的是 Promise,而原生 Promise 的 then 是自有属性,不会走 get 拦截。必须在 get 中显式拦截 then 和 catch 并委托给内部 Promise:
get(target, prop) {
if (prop === 'then' || prop === 'catch' || prop === 'finally') {
return target.promise[prop].bind(target.promise);
}
// ...其余逻辑
}
另一个坑是 this:用户可能写 const fn = rpc.user.update; fn({id: 1}),此时 apply 中的 thisArg 是 undefined,但路径信息存在 target.path 里,所以不能依赖 thisArg 恢复上下文。
最后,网络错误(如超时、断连)必须原样暴露,而不是吞掉变成 undefined。建议统一用 reject(new Error(...)),并把原始 response.status 和 response.url 加入错误信息,方便调试。











