0

I am trying to convert a normal function to an async generator function, it needs to have the same props and prototype. The way that I did it so far was by copying all the descriptors from the async generator function and by using Object.setPrototypeOf

function normalFunc () {
//...
}
async function * asyncGenFunc () {
//...
}

Object.defineProperties(normalFunc, Object.getOwnPropertyDescriptors(asyncGenFunc))
Object.setPrototypeOf(normalFunc, Object.getPrototypeOf(asyncGenFunc))

As I understand it, Object.setPrototypeOf is slow even though I can't see the slowness myself. Is there a better way or is this way not slow in the specific scenario and the tip in MDN isn't about this case.

EDIT: As for the why do I want to do this, here is a very basic version of the function I am making:

const errorHandle = function (func) {
  return function(...args) {
    try {
       let result
       
       result = func.apply(this, args)
       
       if (isObject(result) && result.catch !== undefined) {
         result = result.catch(err => console.error(err))
       }
      
       return result
    } catch(err) {
      console.error(err)
    }
  }
}

const handledAsyncGenFunc = errorHandle(async function * (){})

I want handledAsyncGenFunc to behave exactly the same as a the original async generator function, but to be error-handled. Don't judge the example too much, I only added the bare minimum for the question.

  • 1
    What would be the point of this? Inside `normalFunc()` you can't use `await` and you can't use `yield`. – Pointy Jul 06 '21 at 14:36
  • How are you using `handledAsyncGenFunc`? Usually you wouldn't care what its prototype (or `.prototype`) is. Also the function expression you used in the example has no custom properties anyway. – Bergi Jul 06 '21 at 14:51
  • 1
    If you absolutely have to, `Object.setPrototypeOf` is fine to use, and [there is indeed no alternative](https://stackoverflow.com/a/37927424/1048572). – Bergi Jul 06 '21 at 14:56
  • I didn't want to write out the whole function here in stack overflow. I go over all the function properties and error-handle them too then assign them to the newly created error-handled version. I am doing this for other people to use, so I don't want them to lose functionality because they error-handled their async generator function. – Ivan Georgiev Jul 06 '21 at 14:57
  • Are you wanting to create a kind of middleware, so async generator logs errors..? – Keith Jul 06 '21 at 14:59
  • 1
    @IvanGeorgiev "*go over all the function properties and error-handle them too*" - what does that mean? It's just a function, not a class with methods or something, is it? And even if it was, people will want to be explicit about where errors should be handled and where not. It's expected that these wrapped functions won't behave the same as the original anyway - after all, their exceptions might have been intercepted and they just return `undefined` instead. – Bergi Jul 06 '21 at 15:02
  • @Bergi thanks for the link to the other question, it seems I have to use `Object.setPrototypeOf`. @Keith yes, basically any function. Further I have another function which converts any data type no matter how deep to a version whose every function is error handled and still the original provided is not augmented. – Ivan Georgiev Jul 06 '21 at 15:02
  • @Bergi I have a whole package, I don't want to copy paste it here. You can look at it here - [link](https://github.com/ivangeorgiew/tied-pants) – Ivan Georgiev Jul 06 '21 at 15:05
  • 1
    Ok thanks, that clarifies the use case, but doesn't really explain why you would want to clone the function instead of just wrapping it. Even in all the examples in your readme, the developer basically explicitly calls `tieUp` to get a nicer version of a `try`/`catch` wrapper - they do that consciously, not expecting that the returned function is identical to the argument. – Bergi Jul 06 '21 at 15:13
  • 1
    It seems you have a different, bigger issue though: an async generator function returns an async generator, which doesn't have a `.catch()` method you can hook onto – Bergi Jul 06 '21 at 15:14
  • @Bergi you are correct, I tested it with async gen and regular generator and the errors really aren't caught currently. I will look into how to solve that. – Ivan Georgiev Jul 06 '21 at 15:19
  • @Bergi managed to fix it with with a wrapper generator function which utilizes `yield *result` – Ivan Georgiev Jul 07 '21 at 07:46

1 Answers1

0

From what I can gather your after a kind of middleware for async generators.

This is actually easier than you think, you can just create wrapper function that that just passes re yields inside a loop.

eg.

const wait = ms => new Promise(r => setTimeout(r, ms));

async function *gen1() {
  yield 1;
  await wait(1000);
  yield 2;
  await wait(1000);
  throw "error in gen1";
}

function *gen2() {
  yield 1;
  throw "error in gen2 (normal generator)";
}

async function *handleError(fn) {
  try {
    for await (const x of fn) yield x;
    // yield * fn;
  } catch (e) {
    console.log('Caught a generator error (async or normal):');
    console.error(e);
    //you could also re-throw here
    //if your just want to log somewhere
    //and keep program logic
    //throw e;
  }
}

async function test1() {
  for await (const a of handleError(gen1())) {
    console.log(a);
  }
}

async function test2() {
  for await (const a of handleError(gen2())) {
    console.log(a);
  }
}

test1().finally(() => test2());

One thing to note, when you wrap the function you always assume it's an async generator that returned, even if you pass a normal one. This is because await will work with none Promise's, but of course it's impossible for this to work the other way round. IOW: you will need to use for await (x of y) { on the wrapper function and not just for (x of y) {

Keith
  • 22,005
  • 2
  • 27
  • 44