如何在通过代理执行另一个方法之前始终应用一个方法?

问题描述 投票:0回答:3

我有以下代码,我想在继续调用

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!
javascript function proxy interceptor control-flow
3个回答
0
投票

不使用代理的可行方法是使用异步

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>


0
投票

这里的主要问题是

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; }


-1
投票

这可以使用“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!");
})();

希望有帮助

© www.soinside.com 2019 - 2024. All rights reserved.