0

I'm setting up a mongoDB endpoint with NodeJS. Implementing this backend I seem to have a problem with the code where the function static async injectDB sets a global variable let restaurants which another function static async getRestaurants accesses, but then it turned into undefined

import mongodb from "mongodb"
const ObjectId = mongodb.ObjectID

let restaurants

export default class RestaurantsDAO {|


static async injectDB(conn) {
   if (restaurants) {
      return
   }
   try {
       restaurants = await conn.db(process.env.RESTREVIEWS_NS).collection("restaurants") 
   } catch (e) {
     console.error(
      `Unable to establish a collection handle in restaurantsDAO: ${e}`,
     )
   }
}

static async getRestaurants({
   filters = null,
   page = 0,
   restaurantsPerPage = 20,
   } = {}) {


  console.log(restaurants)   // undefined

  ... 

getRestaurants is of course called at a much later point than injectDB, if I console.log(restaurants) in that function, it writes out its values. But its undefined when the other function is called. Why is that?

The injectDB function is called at server start, while the getRestaurants is called when someone acceesses the endpoint.

An alternative solution is to open the connection to DB in the getRestaurants function, is that best practice?

See full code for restaurantsDAO.js

bogen
  • 9,954
  • 9
  • 50
  • 89
  • 1
    Depends; but you're relying on timing to access a variable that won't be initialized until an arbitrary time in the future--not generally an effective way to do async programming. – Dave Newton Jul 09 '21 at 18:47
  • @DaveNewton thanks. I'm not trying to build a very effective way of doing it, just stuck on my first ever attempt at a mongoDB backend, so would be more helpful if you have suggestions on how to share this variable between the functions – bogen Jul 09 '21 at 18:56
  • 2
    You don't; you approach it as normal JS async programming. I'd *strongly* suggest some tutorials/examples. The [canonical SO answer](https://stackoverflow.com/q/14220321/438992) is likely to be somewhat confusing without some basics. – Dave Newton Jul 09 '21 at 19:02
  • 1
    @DaveNewton also useful [Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference](https://stackoverflow.com/q/23667086) – VLAZ Jul 09 '21 at 19:14
  • Your title is wrong. **Later** it will get its value. However **now** (the place you console.log) it is undefined. Remember, **later** it will be defined. The `await` keyword allows you to wait for **later** (alternatively, the `.then()` method allows you to execute code **later** so you can use the value) – slebetman Jul 09 '21 at 19:28
  • Seems weird no one else that followed the course had this problem though, maybe i'm missing something – bogen Jul 09 '21 at 21:58
  • @slebetman but the console log is called much later though, since that is called when an endpoint returns from a GET, while the injectDB is called at server start – bogen Jul 09 '21 at 22:00
  • In that case it should work. You should add that information to your question. – slebetman Jul 10 '21 at 03:58
  • 1
    One issue I think why nobody is getting your question is we don't see how and were you're calling those functions. And yes, probably most experienced javascript developers would not know what course you're talking about. – slebetman Jul 10 '21 at 04:01
  • @slebetman i've updated the question with the details. I wonder if its best practice to do the conn.db in the getRestaurants endpoint? – bogen Jul 10 '21 at 10:54

1 Answers1

1

Be aware that you cannot know if the await code has finished unless you check for it. It can really help to put console.logs everywhere! See this example:

export default class RestaurantsDAO {

    static restaurants

    static async injectDB(conn) {
        if (RestaurantsDAO.restaurants) {
            return
        }
        try {
            console.log("start loading")
            RestaurantsDAO.restaurants = await conn.db(process.env.RESTREVIEWS_NS).collection("restaurants")
            console.log("finished loading restaurants!")
        } catch (e) {
            console.error(
                `Unable to establish a collection handle in restaurantsDAO: ${e}`,
            )
        }
    }

    static showRestaurants() {
        if (RestaurantsDAO.restaurants) {
            console.log("restaurants are loaded")
        } else {
            console.log("restaurants not yet loaded")
        }
    }
}

So if you call injectDB anywhere else in your code, that doesn't guarantee that restaurants is filled right away.

import RestaurantsDAO from "./restaurantsdao.js"

RestaurantsDAO.injectDB(..)           // console: "start loading"
RestaurantsDAO.showRestaurants()      // console: "restaurants not yet loaded"
// console: "finished loading" (because await has finished)

BTW I think it makes more sense if you make the restaurants variable part of the class, instead of defining it outside of the class on the module.

Kokodoko
  • 26,167
  • 33
  • 120
  • 197
  • Yeah this works and confirms it's not really await/async i have a problem with. It's the RestaurantsDAO.restaurants is nulled out between injectDB and the other callback. Maybe i have to add a new SO question for that case though – bogen Jul 10 '21 at 14:12
  • 1
    You could make the `injectDB` function also return a promise by using `return await conn.db()`, and then your external code can use `restaurants = await RestaurantsDAO.injectDB()`. That way, you don't even have to store the results in the static variable at all. – Kokodoko Jul 10 '21 at 15:35
  • So you recommend to not use global variables together with `async/await`? I have two async functions and need the same url in both functions. Should I declare the url in both? – Timo Aug 18 '22 at 17:23