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.

New Dev
  • 48,427
  • 12
  • 87
  • 129
Fcoder
  • 9,066
  • 17
  • 63
  • 100
  • 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. – jfriend00 Sep 21 '15 at 03:27
  • @jfriend00: As i say in the question, i know all of what you say, But i need a proper way to overcome this. Any idea? – Fcoder Sep 21 '15 at 03:29
  • 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. – jfriend00 Sep 21 '15 at 03: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. – slebetman Sep 21 '15 at 03: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. – Buzinas Sep 21 '15 at 03:38
  • 1
    A bunch of questions with examples of sequencing async operations: http://stackoverflow.com/questions/32028552/es6-promises-something-like-async-each/32028826#32028826, http://stackoverflow.com/questions/29880715/how-to-synchronize-a-sequence-of-promises/29906506#29906506, http://stackoverflow.com/questions/29454785/async-js-and-series-issue/29454856#29454856, http://stackoverflow.com/a/29906506/816620 – jfriend00 Sep 21 '15 at 03:46
  • 1
    @Buzinas - node.js 4.0 does not add anything new in regards to this. All potential solutions such as promises or the async library where in 0.12 too. Besides nobody has actually marked this is a dup of anything yet. – jfriend00 Sep 21 '15 at 03:46
  • @jfriend00: Here i read that if i use Promises, its possible to have problems with other libraries. it's true? http://stackoverflow.com/a/27724416/1037845 – Fcoder Sep 21 '15 at 03:50
  • 1
    @Fcoder - no. Promises don't cause problems with other libraries. Promises are the modern way of coordinating and managing and sequencing asynchronous operations and are now widely supported. If you have multiple async operations in play and care about doing robust error handling, you will find it extremely difficult without an asynchronous feature like Promises. I myself use the Bluebird promises library in all my node.js development because it contains many useful features beyond the built-in promises. – jfriend00 Sep 21 '15 at 03:51
  • @jfriend00: you say "promises are built into node.js ". i dont get this. if its built in, the what about that Promise libraries like q? what are they? i have to use one of them? – Fcoder Sep 21 '15 at 03:55
  • 1
    Promises are part of the ES6 standard and are implemented in node.js. Libraries like Q and Bluebird offer additional features beyond the standard which folks like me find useful. It's no different than someone in client development deciding to use jQuery because it adds useful features beyond the standard DOM implementations. – jfriend00 Sep 21 '15 at 03:59
  • 1
    @jfriend00 it adds, since it adds *generators* and you can simulate the `async/await` with them. – Buzinas Sep 21 '15 at 03:59
  • 1
    @Buzinas - then feel free to write an answer that uses those. How to solve callback hell with multiple async operations is a question that has been asked **hundreds** of times here on StackOverflow. That's my point. And, there are **thousands** of articles on the choices for solving this problem on the web. This question shows a lack of outside research and a willingness to learn one of the techniques the OP is already aware of and just do it. If it was easier to find dups here, I would have marked it as such. – jfriend00 Sep 21 '15 at 04:01

3 Answers3

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.

Buzinas
  • 11,597
  • 2
  • 36
  • 58
  • 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. – chriskelly Sep 21 '15 at 04:46
  • Don't forget to `reject` the promise if there is an error! Moreover, you probably should use a [promisification](http://stackoverflow.com/q/22519784/1048572) function from your promise library. Or just use the promise that `exec()` returns (?). – Bergi Sep 21 '15 at 10:45
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);
       });
   });
};
slebetman
  • 109,858
  • 19
  • 140
  • 171
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)
  })
});