4

I have some objects that I am fetching from a web server. Some of the attributes of the objects require additional async parsing, but I want to do that lazily for performance reasons. I am trying to use a proxy to intercept the access and then parse the attribute if accessed. My handler would look something like this

class MyClass {
   type() {
      return "my new class";
   }
}

let myObject = new MyClass();

let proxy = new Proxy(myObject, {
  get: async(target, prop, receiver) => {
    if (prop === "attribute") {
      let newProp = prop + "_parsed";
      if (!(newProp in target)) {
        return await (new Promise((resolve, reject) => { resolve("parsed value") }));
      }
      else {
        return target[newProp];
      }
    }
    else {
      return Reflect.get(target, prop, receiver);
    }
  },
});

console.log(proxy.attribute);  // "parsed value"
console.log(proxy.type()); // "my new class"

If this worked, I would expect to see

parsed value
my new class

but I am getting the following:

{}
{
  "message": "Uncaught TypeError: proxy.type is not a function",
  "filename": "https://stacksnippets.net/js",
  "lineno": 41,
  "colno": 19
}

Is there any way I can return an asynchronous value through a proxy but have the calling code think it is just accessing an attribute? I am even open to forcing the "parse" logic (represented by the promise) to be synchronous, however that has portions that are async so I am not sure how to do that.

user3840170
  • 26,597
  • 4
  • 30
  • 62
echappy
  • 533
  • 5
  • 13

1 Answers1

4

You cannot (should not) use an async method for the get trap. These always return promises, and you want .type to return a function. You'd want to use

async function parseValue() {
  await new Promise(resolve => setImmediate(resolve));
  return "parsed value";
}
let proxy = new Proxy(myObject, {
  get(target, prop, receiver) {
    if (prop === "attribute") {
      let newProp = prop + "_parsed";
      if (!(newProp in target)) {
        target[newProp] = parseValue();
      }
      return target[newProp];
    } else {
      return Reflect.get(target, prop, receiver);
    }
  },
});
…
console.log(await proxy.attribute);
console.log(proxy.type());
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Thank you for the reply, but note the entire reason I am doing this is to avoid the "await" in the "console.log". In my actual application, this value will be used in custom templates, and I want to hide the async behavior from users creating the templates. the Proxy provided a good way to intercept the request to the attribute, but I am having trouble hiding the async nature. – echappy Oct 15 '20 at 15:44
  • After searching and trying different things (like "deasync"), I basically landed at I will need to accept the fact that "await" will need to be called there. I couldn't find a clean/safe way to force the async commands into a sync-like interface. "require("deasync").sleep(delay);" was the closest I got but it didn't seem safe – echappy Oct 15 '20 at 20:05
  • 2
    You cannot hide async behaviour with a proxy and/or `async function`s. In javascript, you should embrace asynchrony instead of trying to enforce synchrony. `deasync` indeed is a horrible hack that leads to unidiomatic and [imo errorprone](https://stackoverflow.com/a/25447289/1048572) code. – Bergi Oct 15 '20 at 20:24