Joseph
Joseph

Reputation: 1996

How can I run this 3 node.js functions asynchronously using async.js?

I have 3 functions which I want to run asynchronously and when they are all done run another function:

app.get('/', function (req, res) {

  var homePosts = {
    newsest: [],
    reviewed: [],
    mostPopuler: [],
    viewed: []
  };

  // fetch newest pages and asign the result to 'homePosts.newest '
  function fetchNewestPages() {
    Post.find({ "type": "public", "featuredImage": { "$exists": true } },"_id title briefDes featuredImage", function (err, posts) {
      if (err) {
        req.flash('error', 'An unknown error was occured.');
        res.redirect('back');
      } else {
        homePosts.newsest = posts;
      }
    }).limit(4).sort( { date : -1 } );
  }

  // fetch most reviewed pages and asign the result to 'homePosts.reviewd '
  function fetchMostReviewedPages() {
    Post.find({ "type": "public", "featuredImage": { "$exists": true } },"_id title briefDes featuredImage", function (err, posts) {
      if (err) {
        req.flash('error', 'An unknown error was occured.');
        res.redirect('back');
      } else {
        homePosts.reviewed = posts;
      }
    }).limit(4).sort( { commentsNumber : -1 } );
  }

  // fetch most popular pages and asign the result to 'homePosts.mostPopuler '
  function fetchMostPopularPages() {
    Post.find({ "type": "public", "featuredImage": { "$exists": true } },"_id title briefDes featuredImage", function (err, posts) {
      if (err) {
        req.flash('error', 'An unknown error was occured.');
        res.redirect('back');
      } else {
        homePosts.mostPopuler = posts;
      }
    }).limit(4).sort( { likesNumber : -1 } );
  }

 // now run all 3 functions and when they are done render home page with the homePosts object which contains proper pages
  async.parallel([

    fetchNewestPages,
    fetchMostReviewedPages,
    fetchMostPopularPages

  ], function (err) { // it doesn't run at all
    if (err) throw err;
    console.log(homePosts);
    res.render("home", {homePosts}); // render home page with the proper pages 
  });


});

hope that you got what the code does, here is the description of what the code does:

  1. there is homePosts object which will have the proper pages to be displayed on the home page
  2. Then we have a function which will fetch 4 newest pages from database and then assign them to homePost.newest
  3. Then a function which will assign the 4 pages that have mosts comments to homePost.reviewed
  4. the third function like the 2 above functions assign most popular pages to homePost.mostPopular
  5. Now async.js should do its job, run those 3 functions simultaneously and then render a home page with the homePosts object, this is the part I have problem

The last function which will render home page doesn't run at all. Where is my problem? is there any way to run those 3 functions simultaneously and then run the last function which will render a page?

UPDATE:

I've managed to do that in this way but they are not running simultaneously, they are running one after another.

// fetch newest pages and asign the result to 'homePosts.newest '
  function fetchNewestPages(cb) {
    Post.find({ "type": "public", "featuredImage": { "$exists": true } },"_id title briefDes featuredImage", function (err, posts) {
      if (err) {
        req.flash('error', 'An unknown error was occured.');
        res.redirect('back');
      } else {
        homePosts.newsest = posts;
        cb();
      }
    }).limit(4).sort( { date : -1 } );
  }

  // fetch most reviewed pages and asign the result to 'homePosts.reviewd '
  function fetchMostReviewedPages(cb) {
    Post.find({ "type": "public", "featuredImage": { "$exists": true } },"_id title briefDes featuredImage", function (err, posts) {
      if (err) {
        req.flash('error', 'An unknown error was occured.');
        res.redirect('back');
      } else {
        homePosts.reviewed = posts;
        cb();
      }
    }).limit(4).sort( { commentsNumber : -1 } );
  }

  // fetch most popular pages and asign the result to 'homePosts.mostPopuler '
  function fetchMostPopularPages(cb) {
    Post.find({ "type": "public", "featuredImage": { "$exists": true } },"_id title briefDes featuredImage", function (err, posts) {
      if (err) {
        req.flash('error', 'An unknown error was occured.');
        res.redirect('back');
      } else {
        homePosts.mostPopuler = posts;
        cb();
      }
    }).limit(4).sort( { likesNumber : -1 } );
  }

  fetchNewestPages(function () {
    fetchMostReviewedPages(function () {
      fetchMostPopularPages(function () {
          res.render("home", {homePosts});
      });
    });
  });

Upvotes: 0

Views: 89

Answers (3)

Umair Farooq
Umair Farooq

Reputation: 1702

Your problem is you are not having a callback parameter in any of your functions. Remember, you have to call the callback method when one function's processing is completed.

What I do in my practice is use async.constant as the first method of async.waterfall or async.parallel and pass the data that is to be used in the async methods. In your case it can be the search criterion for all three methods. If no data is to be used in async methods, then I just pass an empty JS object.

Using async.constant helps me in two things.

  1. Pass data to be used in async methods.
  2. Get results from async methods in that passed object.

In your case, the async.constant method will have homePosts object.

    app.get('/', function (req, res) {

      // fetch newest pages and asign the result to 'homePosts.newest '
      function fetchNewestPages(data, callback) {
          Post
          .find({ "type": "public", "featuredImage": { "$exists": true } },"_id title briefDes featuredImage")
          .limit(4)
          .sort( { date : -1 } )
          .exec(function (err, posts) {
             if (err) {
               //If we pass first parameter as non-null, the control is passed to the last optional callback skipping all other functions in case of async.waterfall and not waiting for other functions to complete in case of async.parallel
               return callback('An unknown error was occured.');  
             } else {
               data['newsest'] = posts; //since homePosts is data object inside this function
               If this function is completed successfully then we pass first parameter as null (no error) and second parameter as our object. As the strategy is parallel, all three functions will be editing the same object 'homePosts'
               return callback(null, data);
             }
          });
      }

      // fetch most reviewed pages and asign the result to 'homePosts.reviewd '
      function fetchMostReviewedPages(data, callback) {
          Post
          .find({ "type": "public", "featuredImage": { "$exists": true } },"_id title briefDes featuredImage")
          .limit(4)
          .sort( { commentsNumber : -1 } )
          .exec(function (err, posts) {
             if (err) {
               //read comment in first function
               return callback('An unknown error was occured.');
             } else {
               data['reviewed'] = posts; //since homePosts is data object inside this function
               //read comment in first function
               return callback(null, data);
             }
          });
      }

      // fetch most popular pages and asign the result to 'homePosts.mostPopuler '
      function fetchMostPopularPages(data, callback) {
          Post
          .find({ "type": "public", "featuredImage": { "$exists": true } },"_id title briefDes featuredImage")
          .limit(4)
          .sort( { likesNumber : -1 } )
          .exec(function (err, posts) {
             if (err) {
               //read comment in first function
               return callback('An unknown error was occured.');
             } else {
               data['reviewed'] = posts; //since homePosts is data object inside this function
               //read comment in first function
               return callback(null, data);
             }
          });
      }

      var homePosts = {
        newsest: [],
        reviewed: [],
        mostPopuler: [],
        viewed: []
      };

      // now run all 3 functions and when they are done render home page with the homePosts object which contains proper pages

      async.parallel([
        async.constant(homePosts),
        fetchNewestPages,
        fetchMostReviewedPages,
        fetchMostPopularPages
      ], function (err, data) {
            //once all functions complete their execution and their callback method is called, with or without error, this method will be called. 
            if (err) {
              req.flash('error', err);
              res.redirect('back');
            } else {
              console.log(data);
              res.render("home", {data}); // render home page with the proper pages 
            }
      });
});

Hope that solves your problem and clear your concept a bit more.

Upvotes: 2

Robert Moskal
Robert Moskal

Reputation: 22553

The async library works with functions that use callbacks. None of yours do.

Either rewrite them in callback form, or use something like Promise.all:

Promise.all(
    [fetchNewestPages,
    fetchMostReviewedPages, 
    fetchMostPopularPages])
        .then(res => console.log(res[0], res[1], res[2]))
         .catch(err => console.log(err))

Upvotes: 1

t33n
t33n

Reputation: 390

Hope this helped you

console.log('start');

// fetch newest pages and asign the result to 'homePosts.newest '
function fetchNewestPages() {
console.log('1')
}

// fetch most reviewed pages and asign the result to 'homePosts.reviewd '
function fetchMostReviewedPages() {
console.log('2')
}

// fetch most popular pages and asign the result to 'homePosts.mostPopuler '
function fetchMostPopularPages() {
console.log('3')
}


fetchNewestPages();
console.log('1 DONE');
fetchMostReviewedPages();
console.log('2 DONE');
fetchMostPopularPages();
console.log('3 DONE');

I work also alot with interval. As example If I had alot of callbacks and something is suddenly not synchron then this trick can be nice

var obj = {}

// to somelong stuff here and the result is var result
var result = 'this was generated in the fictive long process above'
objectme.obj = result // those object string can be used anywhere in the script realy nice.

clearInterval(testinterval); // <-- do also here a clearinterval
var testinterval = setInterval(function(){

if (objectme.obj) {
clearInterval(testinterval);
//.. the interval only stop here at the if. you can do anything here. also you can make a timeout. This will force the script to run as you wish it
}

},10000);

REALLY IMPORTANT. If you plan to insert long code into clearinterval zone then you need to increase the interval time. If your inserted codes takes longer than the interval, then your code will be execuded 2 times.

However you should do like in the first example. because using interval can be very tricky.

Upvotes: 0

Related Questions