我有以下代码,我想在继续调用
connect
类的所有其他实例方法之前调用实例方法 TelegramClient
。如何利用Proxy
来实现这一点。截至目前,connect
方法尚未被调用。
class TelegramClient {
async connect() {
console.log("Connecting to Telegram...");
// Simulate some asynchronous work
await new Promise(resolve => setTimeout(resolve, 1000));
console.log("Connected!");
}
sendMessage(message: string) {
console.log(`Sending message: ${message}`);
}
}
const telegramClient = new TelegramClient();
// Create a Proxy for the Telegram client
const proxiedTelegramClient = new Proxy(telegramClient, {
async apply(target, thisArg, argumentsList) {
await target.connect(); // Connect to Telegram before invoking the method
const result = Reflect.apply(target, thisArg, argumentsList);
return result;
},
});
proxiedTelegramClient.sendMessage("Hello, Telegram!");
预期产出是...
Connecting to Telegram...
Connected!
Sending message: Hello, Telegram!
不使用代理的可行方法是使用异步
around
方法修饰符的通用实现。后者可以看作是函数包装的特例。
这样的修饰符接受两个函数,
proceed
和handler
以及target
对象作为其3个参数。它确实返回一个异步函数,该函数将再次返回(假定异步)handler
(回调)函数的等待结果。后者确实在(可选)提供的 target
的上下文中被调用,同时还传递了 proceed
函数、其自己的 handler
引用和修改后的函数的 arguments
数组。
因此,基于这样的修改器,OP可以通过修改例如来实现预期的行为:客户端实例的
sendMessage
方法像这样...
// client instantiation.
const telegramClient = new TelegramClient;
// client's handler modification.
telegramClient.sendMessage = asyncAround(
telegramClient.sendMessage,
connectClientBeforeProceed,
telegramClient,
);
...其中
connectClientBeforeProceed
是 handler
函数,它准确地实现了 OP 正在寻找的内容...
“...在继续调用 TelegramClient 类的所有其他实例方法之前连接[客户端]”
...示例代码...
// implementation of the client specific async `around`-handler.
async function connectClientBeforeProceed(proceed, handler, args) {
const client = this;
// ... always connect client ...
await client.connect();
// ... before proceeding with any other method.
return (
await proceed.apply(client, args)
);
}
// client instantiation.
const telegramClient = new TelegramClient;
// client's method-modification.
telegramClient.sendMessage = asyncAround(
telegramClient.sendMessage,
connectClientBeforeProceed,
telegramClient,
);
// client's modified handler invocation.
telegramClient.sendMessage("Hello, Telegram!");
// expected logging ..
//
// - Connecting to Telegram...
// - Connected!
// - Sending message: Hello, Telegram!
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
// client implementation.
class TelegramClient {
async connect() {
console.log("Connecting to Telegram...");
// Simulate some asynchronous work
await new Promise(resolve => setTimeout(resolve, 1000));
console.log("Connected!");
}
sendMessage(message) {
console.log(`Sending message: ${ message }`);
}
}
// implementation of an async `around` modifier.
function asyncAround(proceed, handler, target) {
return async function (...args) {
return (
await handler.call(target ?? null, proceed, handler, args)
);
};
}
</script>
这里的主要问题是
apply
不是 async
,标记 async
不会改变这一点。
与其尝试拦截
sendMessage
方法,另一种选择是将函数自动包装在 get
内。
下面是一个例子,我还使用了
Set
来确保在获取过程中 method
不会再次被包裹。还添加了一个连接属性,因为发送另一条消息不需要另一个连接。
更新:
跟踪实例和代理私有数据,比您想象的要复杂一些。带有代理的
this
的上下文将更改为代理(有道理),因此下面我已更新以希望考虑多个实例,并保持数据私有,无论您是通过代理还是直接访问目标。这里的技巧是使用 Symbol 来存储代理的原始目标,然后您可以使用 weakMap
来保存您的私有实例数据。
const PROXY_TARGET = Symbol('PROXY_TARGET');
const _privData = new WeakMap();
const privData = t => {
let ret = _privData.get(t[PROXY_TARGET] ?? t);
if (!ret) {
ret = {
connected: false,
wrapped: new Set()
}
_privData.set(t, ret);
}
return ret;
}
class TelegramClient {
async connect() {
const priv = privData(this);
if (priv.connected) return;
priv.connected = true;
console.log("Connecting to Telegram...");
// Simulate some asynchronous work
await new Promise(resolve => setTimeout(resolve, 1000));
console.log("Connected!");
}
async sendMessage(message) {
console.log(`Sending message: ${message}`);
}
}
const handler = {
get(target, prop, receiver) {
const priv = privData(target);
if (prop === PROXY_TARGET) return target;
if (['connect'].includes(prop) || priv.wrapped.has(prop) ) {
return Reflect.get(...arguments);
}
const fn = target[prop];
function wrap() {
return target.connect().then(() => {
return fn(...arguments);
});
}
priv.wrapped.add(prop);
target[prop] = wrap;
return target[prop];
}
}
async function test(autoConnect) {
console.log(autoConnect ? 'Auto Connect' : 'Manual Connect');
const telegramClient = new TelegramClient();
const proxiedTelegramClient = new Proxy(telegramClient, handler);
if (!autoConnect) await proxiedTelegramClient.connect();
await proxiedTelegramClient.sendMessage("Hello, Telegram!");
await proxiedTelegramClient.sendMessage("Another message.");
}
//let try auto and manual connect.
test(true).then(() => test(false));
.as-console-wrapper { min-height: 100%!important; top: 0; }
这可以使用“get”而不是“apply”处理程序来解决
//... previous code before the proxiedTelegramClient
// Create a Proxy for the Telegram client
const proxiedTelegramClient = new Proxy(telegramClient, {
get(target, property) {
if (property === 'sendMessage') {
return async function (...args) {
await target.connect(); // Connect to Telegram before sending the message
return Reflect.apply(target[property], target, args);
};
}
return target[property];
},
});
但是如果你想保持代码原样,你可以使用
class TelegramClient {
async connect() {
console.log("Connecting to Telegram...");
// Simulate some asynchronous work
await new Promise(resolve => setTimeout(resolve, 1000));
console.log("Connected!");
}
async sendMessage(message: string) {
await this.connect(); // Connect to Telegram before sending the message
console.log(`Sending message: ${message}`);
}
}
const telegramClient = new TelegramClient();
// Create a Proxy for the Telegram client
const proxiedTelegramClient = new Proxy(telegramClient, {
async apply(target, thisArg, argumentsList) {
return Reflect.apply(target, thisArg, argumentsList);
},
});
(async () => {
await proxiedTelegramClient.sendMessage("Hello, Telegram!");
})();
希望有帮助