Sunday 26 January 2020

Proxying an Object with sync and asycn Methods

The other day came to my mind the idea of how to intercept asynchronous method calls. I'm talking about JavaScript and the typical case where we use a Proxy and a get trap to intercept all method calls to one object, running some code before and after calling the original method.

First we have to check if the intercepted method is sync or async (we'll just check if it returns a Promise). If it's async, the main issue is with the "after call" action. We have to run this extra code after the async method is completed and before the client receives this result. Well, it's just a matter of adding a .then() call to the original promise, running our code inside this "then", and returning to the client the promise created by "then" (promise that we'll resolve to the value returned by the initial promise). All in all, we can write a proxy that intercepts properly both sync and async methods in an object like this:

let proxiedObj = new Proxy(obj, {
 get: function(target, property, receiver){
  let it = target[property];
  if (typeof it !== "function"){
   return it;
  }
  return function(){
   console.log("before invoking");
   
   //we're not trapping additional calls done from this method, so we invoke the original method via "target" rather than "this"
   let result = target[property](...arguments);
   //let result = it.call(target, ...arguments);
   
   if (result instanceof Promise){
    return result.then(res => {
     console.log("after completion of async function");
     return res;
    });
   }
   else{
    console.log("after completion of sync function");
    return result;
   }
  };
 }
);

There's an interesting point. We could think of using await in the returned function rather than then(), something like this:

  //I can not do it like this, cause with the async I would be returning a Promise in both cases, so for the invoker of a sync function he gets a promise of X rathern than X, so that's not what he's expecting
   return async function(){
   //I'm not trapping additional calls done from this object
   console.log("before invoking");
   
   let result = target[property](...arguments);
   //it.call(target, ...arguments);
   
   if (result instanceof Promise){
    let resolvedResult = await result;
    console.log("after completion of async function");
    return resolvedResult;
   }
   else{
    console.log("after completion of sync function");
    return result;
   }
  };

This will work for asynchronous methods, but will break synchronous ones. As we know when a function is marked as async, the compiler modifies the function so that it returns a Promise that resolves to the value returned by the return statement. So if a client is calling a function that returns a string and we intercept it by means of the above get trap, he will receive a Promise of a string...

I've uploaded it to a gist

No comments:

Post a Comment