1

I am working in Node and trying to load the next sequence from my db. I am able to access the db, load and return the sequence within my function, but I am not able to access it outside of the function.

function getRunId() {
        counters.findOne({_id: 'Run_ID'}, function(err, resp) {
            if(err) {
                console.log(err);
            }
            console.log('Seq: ' + resp.sequence); // Console Output = Seq: 1234
            return resp.sequence;
        });
    };

    var currentRunId = getRunId(); 
    console.log('Run_ID: ' + currentRunId); // Console Output = CRID: undefined

I've checked several pages worth of Stack Overflow issues relating to using callback's, async (node module), how to properly return values in the function, etc... but none of them get me closer to accessing currentRunId outside of the function.

Is this issue further complicated by the use of Mongo queries inside my function?

Fairplay89
  • 147
  • 3
  • 15
  • `getRunId()` doesn't return anything, so `currentRunId` will always be undefined. The return statement in your code is for a function that you send to `counters.findOne()`. Its documentation should tell you how to use it. – Biffen Dec 09 '16 at 20:01
  • I've already checked [the documentation](https://docs.mongodb.com/v3.0/tutorial/create-an-auto-incrementing-field/) and recommended examples for this. The recommendation looks like Dalorzo's answer below, but unfortunately this does not work either. – Fairplay89 Dec 09 '16 at 20:23

2 Answers2

2

For anyone stumbling on this later, start by reading this answer.

I've dealt with this a few times so I understand the frustration. You are trying to mix sync and async code by doing this:

var currentRunId = getRunId(); 
console.log('Run_ID: ' + currentRunId);

The trouble is that console.log('Run_ID: ' + currentRunId) is called immediately after you invoke getRunID() by assigning it to current RunID, and getRunID() resolves after console.log('Run_ID: ' + currentRunId), causing the currentRunId variable to be undefined.

But, you have some options to deal with this. Option one is to return a callback, and log the results of the callback instead. Option 2 is to use an ES6 promise. To use option 2, you need node version 7, and you need to use 'use strict' in your code.

Here are 3 examples built around a function stub that spoofs the results of findOne(). The getRunIdA() is your function, and getRunIdB, and getRunIdC are two example solutions to your current problem.

'use strict'

// A function stub which represents a simplified version of findOne.
// Accepts callback and returns a callback with the results of data
function findOne (callback) {
  var data = {
    sequence: 6
  }
  return callback(null, data)
}

// This is a simplified version of your function, which reproduces the undefined result
function getRunIdA () {
  findOne(function (err, resp) {
    if (err) {
      console.log(err)
    }
    console.log('Seq: ' + resp.sequence)
    return resp.sequence
  })
}

// This is your function with a callback
function getRunIdB (callback) {
  findOne(function (err, resp) {
    if (err) {
      console.log(err)
    }
    console.log('Seq: ' + resp.sequence)
    return callback(resp.sequence)
  })
}

// This is your function with a promise
var getRunIdC = new Promise(function (resolve, reject) {
  resolve(findOne(function (err, resp) {
    if (err) {
      console.log(err)
    }
    return resp.sequence
  }))
})

// Invoke your funciton; get undefined
var currentRunID = getRunIdA()
console.log('Run_ID: ' + currentRunID) // Run_ID: undefined

// Invoke getRunIdB using callback, get 6
getRunIdB(function (id) {
  console.log('Run_ID: ' + id) // Run_ID: 6
})

// Invoke getRunIdC with a promise; get 6
getRunIdC.then(function (currentRunID) {
  console.log('Run_ID: ' + currentRunID) // Run_ID: 6
})

/*
results for all 3:

Seq: 6
Run_ID: undefined
Seq: 6
Run_ID: 6
Run_ID: 6
*/

Give this a try by saving to your machine and running:

node test.js

Is this issue further complicated by the use of Mongo queries inside my function?

Nope, you just need to pass the results of your query to a promise or a callback so that you can work with the results somewhere else.

I hope this helps!

Edit: OP added the following code in a comment, which I will try to break down and address.

Unfortunately, using getRunIdB results in callback is not defined and using getRunIdC results in currentRunId is not defined

var currentRunID = ''; 
var getRunId = new Promise(function (resolve, reject) { resolve(counters.findOne({_id: 'Run_ID'}, function (err, resp) { 
  if (err) { 
    console.log(err) 
  } 
  return resp.sequence; 
  })) 
}); 
getRunId.then(function (res) { 
  console.log('Run_ID: ' + res.sequence) // Run_ID: 1234 
  currentRunID = res.sequence; 
}) 
console.log(currentRunID); // currentRunID is not defined

Check out an answer I gave to a similar question for more details on the JS concurrency model. Simply put, the getRunID() function is executing asynchronous code. What that means is that getRunID() doesn't get inserted into the message queue that determines what order javascript will execute until it's callbacks are completed. Thus, when you log currentRunID outside of the .then() function, the results is undefined because currentRunID is undefined.

I think that ultimately what OP is trying to do is to export the result of the function so that the something can be done with those results, this needs to be done within a callback like so:

getRunId.then(function (res) { 
  // Do stuff with the run ID here.
}) 
Community
  • 1
  • 1
Joe Creager
  • 56
  • 1
  • 6
  • First off, thanks a bunch for taking the time to answer. Unfortunately, using getRunIdB results in `callback is not defined` and using getRunIdC results in `currentRunId is not defined`. – Fairplay89 Dec 09 '16 at 22:54
  • `var currentRunID = ''; var getRunId = new Promise(function (resolve, reject) { resolve(counters.findOne({_id: 'Run_ID'}, function (err, resp) { if (err) { console.log(err) } return resp.sequence; })) }); getRunId.then(function (res) { console.log('Run_ID: ' + res.sequence) // Run_ID: 1234 currentRunID = res.sequence; }) console.log(currentRunID); // currentRunID is not defined` – Fairplay89 Dec 09 '16 at 22:54
  • Hey Fairplay89, if you look again, you will see that the promise that you used _did_ work, the trouble is that you are trying to log the results of the currentRunID variable outside of the callback function where that variable is assigned. You can't do this due to JavaScript's concurrency model. You also arguably shouldn't do this because assigning the results to a global isn't a great practice. I think what you are trying to do is make it so that you can work with the results of getRunID() somewhere else in your code. I added some additional info to my post. Let me know if this helps. – Joe Creager Dec 09 '16 at 23:12
  • Unfortunately I am still not able to access the run ID outside of these functions. Your last answers mentions using getRunId.then – Fairplay89 Dec 12 '16 at 18:33
  • Can't edit my last comment..... Unfortunately I am still not able to access the run ID anywhere outside of these functions, which is what I'm trying to achieve. Your edited answer mentions using the return value in getRunId.then(), but using this results in `getRunId.then is not a function`. Are there better ways to achieve this (having access to a MongoDB value)? – Fairplay89 Dec 12 '16 at 18:41
  • >Unfortunately I am still not able to access the run ID anywhere outside of these functions – Joe Creager Dec 13 '16 at 17:06
  • You won't be able to do access runID outside of a callback or promise that returns the result of getRunId this with asynchronous code. [this](http://stackoverflow.com/questions/23667086/why-is-my-variable-unaltered-after-i-modify-it-inside-of-a-function-asynchron) is a good explaination of async vs sync code. [MongoDB does not have a synchronous API for javascript](http://stackoverflow.com/questions/12030248/what-is-the-right-way-to-make-a-synchronous-mongodb-query-in-node-js). You need to use a callback or promise to do something with the runID. – Joe Creager Dec 13 '16 at 17:12
1

You are only returning on a callback function but not on the actual function.. Change your code to this:

function getRunId() {
        var result = counters.findOne({_id: 'Run_ID'}, function(err, resp) {
            if(err) {
                console.log(err);
            }
            console.log('Seq: ' + resp.sequence); // Console Output = Seq: 1234

        return resp.sequence;
    });
    return result; //<-- return result of your function is here
};

var currentRunId = getRunId(); 
console.log('Run_ID: ' + currentRunId); 
Dalorzo
  • 19,834
  • 7
  • 55
  • 102