3

I have a function that fetches data from a database:

recentItems = function () {
  Items.find({item_published: true}).exec(function(err,item){
      if(!err)
        return item
  });
};

And I want to use it like this:

var x = recentItems();

But this fails with undefined value due to Async behavior of recentItems. I know that I can change my function to use a callback like this:

recentItems = function (callback) {
  Items.find({item_published: true}).exec(function(err,item){
      if(!err)
        callback(item)
  });
};

And:

recentItems(function(result){
  var x = result;
});

But i dont want to use this method because i have a situation like this. i have a function that should do two operations and pus result to an array and after them, fire a callback and return value:

var calc = function(callback){
   var arr = [];

   var b = getValues();
   arr.push(b);

   recentItems(function(result){
     var x = result;
     arr.push(x);
   });

   callback(arr);
};

In this situation, the value of b pushed to arr and the main callback called and after that value of x fetched from recentItems duo to Async behavior of recentItems. But I need this two operation runs sequentially and one after one. After calculating all of them, then last line runs and the callback fired.

How can I resolve this? I read about the Promises and Async libraries, but I don't know which of them is my answer. Can I overcome this with raw Node.js? If so, I would prefer that.

13
  • 1
    This is probably one of the most popular duplicate questions about node.js. Your operation is ASYNC. You cannot return the result from a synchronous function because the async results happens sometime in the future. If you want to chain multiple operations, then either nest your callbacks or use promises or use the async library. Commented Sep 21, 2015 at 3:27
  • 1
    I gave you three choices in my comment. FYI, promises are built into node.js so they are "raw node.js" now and highly recommended. Commented Sep 21, 2015 at 3:30
  • 1
    @Fcoder: If you are new to async code then the proper way to overcome it is to not overcome it. You already know how to handle it so just do it. Once you have a bit more experience take a look at promises. Note that promises don't remove the need for callbacks, they just restructure your callbacks to remove nesting. So before learning about promises get comfortable with regular callbacks first. Commented Sep 21, 2015 at 3:34
  • 2
    I don't agree with the duplicate flag, since NodeJS 4.0.0 is very new, and there are new ways of doing what the OP wants today. Commented Sep 21, 2015 at 3:38
  • 1
    A bunch of questions with examples of sequencing async operations: stackoverflow.com/questions/32028552/…, stackoverflow.com/questions/29880715/…, stackoverflow.com/questions/29454785/async-js-and-series-issue/…, stackoverflow.com/a/29906506/816620 Commented Sep 21, 2015 at 3:46

3 Answers 3

3

There are some ways of doing what you want, but none of them are ~perfect~ yet.

There is an ES7 proposal of native async/await that will be the callback heaven, but atm, you can do:

  • Nested callbacks (native, but very ugly and unmaintainable code)
  • Promises (good, but still too verbose)
  • Async/Await library (It's an amazing library, but very far from native, and performance isn't cool)
  • ES7 transpiler - you can write the ES7 code today, and it will transpile for you to ES5 (e.g Babel)

But, if you're already using the newest version of NodeJS (4.0.0 as the time of writing) - and if you're not, you really should - the best way of achieving what you want is to use generators.

Combined with a small library named co, it will help you to achieve almost what the ES7 async/await proposes, and it will mostly use native code, so both readability and performance are really good:

var co = require('co');

var calc = co(function *calc() {
  var arr = [];
  var b = getValues();
  arr.push(b);

  var items = yield recentItems();
  arr.push(items);

  return arr;
});

function recentItems() {
  return new Promise(function(resolve) {
    Items.find({item_published: true}).exec(function(err, item) {
      if(!err)
        resolve(item);
  });
}

You can read more about this subject in this awesome Thomas Hunter's blog post.

Sign up to request clarification or add additional context in comments.

2 Comments

You should probably mention that this is ES6 and you can get it by passing the --harmony flag to node 0.11 and later. Also, may I suggest gor readability not wrapping it all in a callback. something like: var run = require('co');function *main () { } run(main) is more familiar looking.
Don't forget to reject the promise if there is an error! Moreover, you probably should use a promisification function from your promise library. Or just use the promise that exec() returns (?).
1

You've almost got it. There is no method to work-around callbacks. However, you can certainly use callbacks to do what you want. Simply nest them:

var calc = function(callback){
   var arr = [];

   getValues(function(b){
       arr.push(b);

       recentItems(function(result){
         var x = result;
         arr.push(x);

         callback(arr);
       });
   });
};

1 Comment

getValues seems to be synchronous in OPs code, doesn't it?
1

You can try something like this. It still nests the callbacks, but the code is a little cleaner.

var callA = function(callback) {
  //Run the first call
  prompt(callback(data));
}


var callB = function(callback) {
  //Some other call
  prompt(callback(data));
}

callA(function(dataA) {
  callB(function(dataB) {
    //Place a handler function here
    console.log(dataA + " " + dataB)
  })
});

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.