gremo
gremo

Reputation: 48909

JavaScript wrap existing function in async one: deal with the result (automatically wrapped into a Promise)?

I'm trying to write a "mixing" for JavaScript classes (controllers, in my app) to automatically "await" for a given function to be resolved, before actually invoke the real methods. Real class methods should receive the resolved value as last argument.

Here is the code of useAwait, where i'm looking for the static class property awaits and wrapping the originalFunc into a new async one. I'm calling the new function passing the original arguments plus the asyncFn call result:

const useAwait = (controller, asyncFn) => {
  controller.constructor.awaits.forEach(func => {
    const originalFunc = controller[func];

    controller[func] = async (...args) => {
      return originalFunc.apply(
        controller,
        [...args, await asyncFn.call(controller)]
      );
    };
  });
}

So when useAwait(ctrl, this.load) is invoked on an instance this class:

class Controller {
  static awaits = ['foo', 'bar'];
  
  promise;
  
  constructor() {
    useAwait(this, this.load);
  }
  
  async foo(e, resolved) {        
    return resolved;
  }
  
  bar(resolved) {
    return resolved;
  }
  
  async load() {
    if (!this.promise) {
      this.promise = new Promise(resolve => setTimeout(() => {
        resolve('Hello World!');
      }, 3000));
    }

    return this.promise;
  }
}

The problem: all seems fine for foo (already async), but it's not for bar: the result is a Promise because now the bar is wrapped in async (wan't before). I know that an async function result is wrapped into a Promise. Codepen example where bar call outputs "[object Promise]".

So the question is: in theory, I should check if the original function is async and if it was not, await for it's return value?

Upvotes: 2

Views: 605

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1074585

...in theory, I should check if the original function is async and if it was not, await for it's return value?"

It wouldn't matter, your wrapper is async; an async function always returns a promise, whether you use await or not. Moreover, your wrapper can't be synchronous, because it needs to call awaitFn (load, in the example) and wait for its result.

If you're going to wrap originalFunction (bar) such that it waits for awaitFn (load) to complete, the wrapped version of it needs to be asynchronous (either async, or return a promise explicitly [or accept a callback, but better IMHO to use promises]). It cannot be synchronous, because awaitFn (load) isn't synchronous.

If the class instance isn't ready for use when you construct it, you might consider using a static method to get an instance instead; the static instance would return a promise that it fulfills with the instance once load is complete. Rough sketch:

class Controller {
    dataFromLoadingProcess;

    constructor(dataFromLoadingProcess) {
        this.dataFromLoadingProcess = dataFromLoadingProcess;
    }

    async foo(e, resolved) {
        // ...optionally use `this.dataFromLoadingProcess`...
        return resolved;
    }

    bar(resolved) {
        // ...optionally use `this.dataFromLoadingProcess`...
        return resolved;
    }

    static async createInstance() {
        await /*...the loading process...*/;
        return new Controller(/*...data from loading process here, perhaps...*/)
    }
}

Upvotes: 4

Related Questions