Search code examples
node.jsmongodbtypescriptmongodb-nodejs-driver

Handling Callbacks in Classes


I'm building classes to find and quickly operate actions on mongodb documents. This is the UserCursor class. (Not talking about MongoDB's cursor)

exports { UserCursor };
class UserCursor {

    private __id: object;

    constructor(query: { _id?: object, otherId?: number }) {
        let { _id, otherId } = query; // Shortens the vars' name
        if (!_id && !otherId) return; // Checks if 1 identifier is provided

        if (_id) { // If a _id is provided
            Users.findOne({ _id }, (err, user) => {
                this.__id = user._id;
            });
        } else if (otherId) { // If a otherId is provided
            Users.findOne({ otherId }, (err, user) => {
                console.log(1); // Debug, you'll see later
                this.__id = user._id;
            });
        }
    }


    // Returns this.__id (which should have been initialized in the constructor)
    get _id() {
        console.log(2)
        return this.__id;
    }

}

When run, the console returns

2
1

I think you got the problem: the mongo callback in the constructor gets on after _id operates. How could I manage that, since the constructor gets activated each time the class is used?


Solution

  • It's not entirely clear to me, what exactly you want to happen and how you use this class but I assume you want to instantiate it and then be able to get _id instantaneously. If it's not the case, you may still get some useful info from my answer. Feel free to provide more details, I will update it.

    So mongodb operations are asynchronous, if you do

    const cursor = new UserCursor(...)
    console.log(cursor._id)
    

    (I assume you want this), first all operations in this thread will run, including the call to get _id(), then the callback code will. The problem with such asynchronous things is that now to use this _id you will have to make all of your code asynchronous as well.

    So you will need to store a Promise that resolves with _id from mongodb and make a method getId that returns this promise like this:

    private __id: Promise<object>
    
    constructor(...) {
      // ...
      if(_id) {
        this.__id = new Promise((resolve, reject) => {
           Users.findOne({ _id }, (err, user) => {
              if(err) return reject(err)
              resolve(user._id)
           });
        })
      } else {
        // Same here
      }
    }
    
    public getId() {
       return this.__id;
    }
    

    Then use it:

    const cursor = new UserCursor(...)
    cursor.getId().then(_id => {
      // Do smth with _id
    })
    

    Or

    async function doStuff() {
      const cursor = new UserCursor()
      const _id = await cursor.getId()
    }
    doStuff()
    

    If you now do it inside some function, you'll also have to make that function async

    Also you could leave a getter like you have now, that will return a promise, but I find it less readable than getId():

    cursor._id.then(_id => { ... })
    
    const _id = await cursor._id