Async issue with function in an argument

I have a function below that relies on another function as argument 1. The problem is it doesn’t wait until the function is done returning a record. It just runs into a conditional block and chooses “record not found.”

How do you solve an async issue like this when you are waiting for an argument to resolve?

Here is the code:


const lookup_shortlink = (long_link) => {
    console.log("long link in parameter, before hitting the findOne ", long_link);

    ShortLink.findOne({
            long_link : long_link
        },
        function (error, object) {
            if (error) {
                console.log('error ', error);
            } else if (object) {
                console.log("object found during lookup", object);
                return object
            }
        }
    )
};


// pass current record or create new and pass back
const return_record_or_create_new = (found_record, long_link) => {
    if (found_record) {
        // record is found >> return it to the front end
        console.log("found record during return step ", found_record);
        return found_record
    } else {
        console.log("record not found");
    }
};


// API Endpoints


router.get('/result', function (req, res, next) {
    const long_url = req.query.long_url;

    return_record_or_create_new(lookup_shortlink(long_url), long_url);

    // render the data to the result page
    res.render('result', { title: "Results", short_url : long_url });

});

EDIT: Also tried this variation with the same results:

const return_record_or_create_new = (long_link) => {
    const found_record = lookup_shortlink(long_link);

    if (found_record) {
        // record is found >> return it to the front end
        console.log("found record during return step ", found_record);
        return found_record
    } else {
        console.log("record not found during return/create step");
    }
};

I tried adding async/await, but the issue remains:

// pass current record or create new and pass back
const return_record_or_create_new = async (long_link) => {
    const found_record = await lookup_shortlink(long_link);

    if (found_record) {
        // record is found >> return it to the front end
        console.log("found record during return step ", found_record);
        return found_record
    } else {
        console.log("record not found during return/create step");
    }
};

Hi @SeanDez,

The issue with your code is that lookup_shortlink is an asynchronous function since it queries the database. So it executes and since you don’t have a return statement in this function it implicitly returns undefined which is a falsy value. Therefore, found_record is always a falsy value too and thats why it logs console.log("record not found");.

There are couple of ways you could solve this problem with:

  1. Callbacks: I’ve taken some liberty with your code and modified some bits, see if it works:
const lookup_shortlink =  (long_link, callback, render) => {
    console.log("long link in parameter, before hitting the findOne ", long_link);

    ShortLink.findOne({
            long_link : long_link
        },
        function (error, object) {
            if (error) {
                console.log('error ', error);
                callback(null);
            } else if (object) {
                console.log("object found during lookup", object);
                callback(object, render);
            }
        }
    )
};


// pass the returned object and render callback to this function
const return_record_or_create_new = (found_record, render_callback) => {
    if (found_record) {
        // record is found >> return it to the front end
        console.log("found record during return step ", found_record);
        //execute render callback if the record found
        return render_callback();
    } else {
        console.log("record not found");
    }
};


router.get('/result', function (req, res, next) {
    const long_url = req.query.long_url;
    const to_render = () => {
        res.render('result', { title: "Results", short_url : long_url });
    }
    lookup_shortlink(long_url, return_record_or_create_new, to_render);
});
  1. Promises:
    I believe mongoose doesnt support promises but it does support Promises like syntax.
    You can use .then() with mongoose function. Example
ShortLink.findOne({
  //if found then resolve
}).then(() => {
  //Do something after successful resolution
});
  1. async-await:
    You’d have to convert other functions including the middleware function to async await too.
router.get('/result', async function (req, res, next) {
    const long_url = req.query.long_url;

    await return_record_or_create_new(lookup_shortlink(long_url), long_url);

    // render the data to the result page
    res.render('result', { title: "Results", short_url : long_url });

});
1 Like

Thanks @ezioda004! I’ve been working on this all day but can’t get it to work. I tried using callbacks as that is a pattern I need to practice. But then I mixed in async too. Even that is not working. Here is my API endpoint:

router.get('/result', async function (req, res, next) {
    const long_url = req.query.long_url;

    // check if the long url is in the db already
    // if yes then set short_url to the collection.short_url
    // if no then generate new record
    const short_url = await lookup_shortlink(long_url, create_new_shortlink_record);
    await console.log("index.js:74 ", short_url);

    // pass the short_url to index.pug
    // render the data to the result page
    res.render('result', { title: "Results", short_url : short_url });

});

and the callbacks:

const lookup_shortlink = async (long_link, not_found_callback) => {
    console.log("long_link from parameter, before hitting the findOne ", long_link);

    await ShortLink.findOne({
            long_link : long_link
        },
        function (error, object) {
            if (error) {
                console.log('error ', error)
            } else if (object.long_link) {
                console.log("object found during lookup", object);
                console.log("object short_link", object.short_link);
                return object.short_link;
            } else {
                console.log("not found during lookup");
                not_found_callback(long_link);
            }
        }
    )
};

const create_new_shortlink_record = (long_link) => {
    const short_link_record = new ShortLink({
        long_link : long_link,
        short_link : process.env.ROOT_DOMAIN + '/r/' + shortid.generate()
    });
    short_link_record.save((error) => {
        if (error) {
            console.log(error);
        }
    });
    console.log('index.js:55', short_link_record.short_link);
    return short_link_record.short_link;

};
    await console.log("index.js:74 ", short_url);

I know that line doesn’t need to be awaited but I was just testing await on it because it returns undefined. The previous steps return the correct information though so something is awry here.

Here is the console output:

[nodemon] starting `node ./bin/www start`
(node:15682) DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect.
Connected to MongoDB
long_link from parameter, before hitting the findOne  testval14
object found during lookup { _id: 5bd4ad739804951700595dd1,
  long_link: 'testval14',
  short_link: 'localhost:3000/r/gvwsUGejQ',
  __v: 0 }
object short_link localhost:3000/r/gvwsUGejQ
index.js:74  undefined
GET /result?long_url=testval14 304 1722.137 ms - -
GET /stylesheets/style.css 304 1.971 ms - -