3

I need to fetch id of user from collection 'users' by calling a function and return it's value.

fetchId = (name) => {
        User.findOne({name: name}, (err, user) => {
            return user._id;
        });
    };

But this implementation returns null. What is the way to fix it?

Adam Jakś
  • 63
  • 1
  • 8

2 Answers2

6

following your example, if you don't want to use promises, you can simply pass a callback from the caller and invoke the callback when you have the result since the call to mongo is asynchronous.

fetchId = (name, clb) => {
  User.findOne({name: name}, (err, user) => {
    clb(user._id);
  });
};

fetchId("John", id => console.log(id));

Otherwise you can use the promise based mechanism omitting the first callback and return the promise to the caller.

fetchId = name => {
  return User.findOne({name: name}).then(user => user.id);
}; 


fetchId("John")
 .then(id => console.log(id));
Karim
  • 8,454
  • 3
  • 25
  • 33
  • Ok, but how to use this first snippet with return value syntax? Ex. { creator_id: fetchId("Ian Corby", id => { /* return id */ } ) } – Adam Jakś Nov 13 '18 at 19:28
  • @AdamJakś - there are only two things that can be returned from a function (fetchId) whose result depends on an async call (findOne): (a) nothing, which is what your code and this answer's first idea returns, (b) a promise, which is this answer's second suggestion. – danh Nov 13 '18 at 20:21
  • @AdamJakś the promise suggestion is better, and once you master it, read up on async/away, which is a small improvement to promise that allows you to use the syntax I think you're looking for. – danh Nov 13 '18 at 20:22
  • @Karim from where you get user in user => user.id snippet? (second example) ;) – Adam Jakś Nov 15 '18 at 12:44
  • it's the result of the promise returned by User.findOne , after the call to the database – Karim Nov 15 '18 at 12:46
0

A third way is a variation of suggestion #2 in @Karim's (perfectly good) answer. If the OP wants to code it as if results are being assigned from async code, an improvement would be to declare fetchId as async and await its result...

fetchId = async (name) => {
    return User.findOne({name: name}).then(user => user.id);
};

let someId = await fetchId("John");
console.log(id)

edit

For any method on an object -- including a property getter -- that works asynchronously, the calling code needs to be aware and act accordingly.

This tends to spread upward in your system to anything that depends on caller's callers, and so on. We can't avoid this, and there's no syntactic fix (syntax is what the compiler sees). It's physics: things that take longer, just take longer. We can use syntax to partially conceal the complexity, but we're stuck with the extra complexity.

Applying this to your question, say we have an object representing a User which is stored remotely by mongo. The simplest approach is to think of the in-memory user object as unready until an async fetch (findOne) operation has completed.

Under this approach, the caller has just one extra thing to remember: tell the unready user to get ready before using it. The code below employs async/await style syntax which is the the most modern and does the most to conceal -- but not eliminate :-( -- the async complexity...

class MyMongoUser {
    // after new, this in-memory user is not ready
    constructor(name) {
        this.name = name;
        this.mongoUser = null;  // optional, see how we're not ready?
    }

    // callers must understand: before using, tell it to get ready!
    async getReady() {
        this.mongoUser = await myAsyncMongoGetter();
        // if there are other properties that are computed asynchronously, do those here, too
    }

    async myAsyncMongoGetter() {
        // call mongo
        const self = this;
        return User.findOne({name: self.name}).then(result => {
            // grab the whole remote object. see below
            self.mongoUser = result;
        });
    }

    // the remaining methods can be synchronous, but callers must
    // understand that these won't work until the object is ready
    mongoId() {
        return (this.mongoUser)? this.mongoUser._id : null;
    }

    posts() {
        return [ { creator_id: this.mongoId() } ];
    }
}

Notice, instead of just grabbing the mongo _id from the user, we put away the whole mongo object. Unless this is a huge memory hog, we might as well have it hanging around so we can get any of the remotely stored properties.

Here's what the caller looks like...

let joe = new MyMongoUser('joe');
console.log(joe.posts()) // isn't ready, so this logs [ { creator_id: null } ];
await joe.getReady();
console.log(joe.posts()) // logs [ { creator_id: 'the mongo id' } ];
danh
  • 62,181
  • 10
  • 95
  • 136
  • Yes, but the problem is I need to implement sytax like that: value = fetchId('John'); so function fetchId should RETURN value (console.log is working properly, but return - not) ;) – Adam Jakś Nov 14 '18 at 13:15
  • @AdamJakś - it can only return nothing or a *promise* to finish later. What it can't return is the result of findOne(), which hasn't run yet at the point fetchId returns. In the callback approach, it doesn't matter what the callback returns. The call back is called after findOne() runs. – danh Nov 14 '18 at 15:45
  • Ok, thank You for the explicit explaining. But - what if I need to use this function in value of property in Object? Just like: { posts [ { content: 'lorem', creator_id: } ]. Do You think it is possible? How to give the creator_id key the user id value? ;) – Adam Jakś Nov 15 '18 at 08:03
  • @AdamJakś - I understand what's troubling you. Please see the extensive amendment I made to the answer. – danh Nov 15 '18 at 16:56
  • Thanks you a lot for great explaining. I implemented this code by adding a function to value property, just like: posts:[ { creator_id: fetchId('john'); } ] and in the function body I added code from your caller's code proposition: async fetchId(name) { let john = new MyMongoUser(name); await john.getReady(); john.mongoId(); } And the problem is now that console returns me an error: Post validation failed: creator_id: Cast to String failed for value "Promise { }" at path "creat or_id" In post schema I have creator_id set as string. Parse string is not a sollution. – Adam Jakś Nov 16 '18 at 12:14
  • @AdamJakś - I'm afraid I didn't quite make myself understood. I've edited once more. The posts structure shouldn't include call fetchId (notice my suggestion doesn't include any method called "fetchId"). I hope this was helpful to you, but I don't think I have anything more to add. Perhaps read more about promises and async code before proceeding with your project. – danh Nov 16 '18 at 19:52