skip to Main Content

I have an instance of a class:

let myInstance = new MyObject();

that looks like this:


export default class MyObject () {
    constructor () {}

    methodOne () {
        // Do stuff …
        return this;
    }

    async methodTwo () {
        let db_query = await Connection.query('<DATABASE QUERY>');

        return this;
    }

    methodThree () {
        // Do stuff …
        return this;
    }

    // etc.
}

This way I can chain methods on the instance:

myInstance
    .methodOne()
    .methodTwo()
    .methodThree()

How can I make sure that methodTwo won’t return (this) until the async/await is resolved?

Doing something like this (won’t work):

let db_query = await Connection.query('<DATABASE QUERY>');

    db_query
        .then(() => {
            return this:
        })

Note: myInstance is itself wrapped in an async/await method.

2

Answers


  1. methodTwo returns a promise, like all async functions do. It’s a promise that will be fulfilled with this, or rejected if the operation it awaits rejects. There’s nothing you can do to make methodTwo wait synchronously for the asynchronous result from Connection.query. JavaScript’s run-to-completion, one-active-thread-per-realm semantics don’t allow that.

    Since methodTwo returns a promise, to use the chaining, you’d have to await the result of methodTwo:

    (await myInstance
        .methodOne()
        .methodTwo())
        .methodThree()
    

    …which isn’t…beautiful, but isn’t too bad. 🙂 Or if you’re doing this where you can’t use await, you’d need then (at minumum, probably also catch):

    myInstance
        .methodOne()
        .methodTwo()
        .then((t) => t.methodThree()); // Or don't use `t` and use `myInstance`
    

    …which is probably even less beautiful. 🙂

    This is just a fundamental aspect of asynchronous operations in JavaScript.

    You could make methodTwo not async and have it return this before the Connection.query operation completes, but I suspect you don’t want to do that.

    Login or Signup to reply.
  2. Things like that are done with Proxy. Here’s a very simple example without multiple chaining:

    class MyObject{
        constructor () {}
    
        methodOne () {
            console.log('methodOne');
            return this;
        }
    
        async methodTwo () {
            
            let db_query = await new Promise(r => setTimeout(r, 1000));
            console.log('methodTwo');
        }
    
        methodThree () {
            console.log('methodThree');
            return this;
        }
    
        // etc.
    }
    
    chainAsync(MyObject.prototype, 'methodTwo');
    
    (async () => {
      
      const obj = new MyObject;
      
      await obj.methodTwo();
      
      await obj.methodOne().methodTwo().methodThree();
      
      console.log('done');
      
    })();
    <script>
    function chainAsync(proto, ...methods){
      const props = Object.getOwnPropertyDescriptors(proto);
      for(const prop in props){
        if(methods.includes(prop)){
          const _original = props[prop].value;
          const value = function(...args){
            return new Proxy(this, {
              get(target, prop){
                if(prop === 'then'){
                  const promise = _original.apply(target, args)
                  return promise.then.bind(promise);
                }
                if(typeof target[prop] === 'function'){
                  return async function(...args2){
                    await _original.apply(target, args);
                    return target[prop](...args2);
                  };
                }
                return Reflect.get(...arguments);
              }
            });
          };
          Object.defineProperty(proto, prop, Object.assign(props[prop], {value}));
        }
      }
    }
    </script>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search