0

I am writing some code which iterates first through an array, and then further iterates through an array contained in the original array.

I am ending up with this weird pattern which I am repeating and I am certain is not optimized. While iterating through the last rssFeeds array item, it changes the value of 'triggerCallback' to true. Later, while iterating through the item array, a conditional checks if both triggerCallback is true and the items array is iterating through its last item, at which point it triggers a callback to be in used in async.js's waterfall pattern.

function countSuccessPosts(rssFeeds, cb){
  var successCounter = 0;

  var triggerCallback = ''

  rssFeeds.forEach(function(feed, index, array){
    if(index == array.length - 1){
      triggerCallback = 'true'
    }

    feed.itemsToPost.forEach(function(item, itemIndex, itemArray){
      if(item.response.id){
         ++successCounter

      }

      if(itemIndex == itemArray.length - 1 && triggerCallback == 'true'){
        cb(null, rssFeeds, successCounter)
      }
    })
  })

}

What's a more optimal way to structure this pattern?

Data Structure: RssFeeds will have up to 5 different itemsToPost elements.

[
  {
    "_id": "55808127b8f552c8157f74a7",
    "name": "",
    "imageUrl": "",
    "url": "http://www.taxheaven.gr/bibliothiki/soft/xml/soft_law.xml",
    "latestDate": "1434056400000",
    "endpoints": [
      {
        "_id": "554f9319bc479deb1757bd2e",
        "name": "Wise Individ",
        "id": 26413291125,
        "type": "Group",
        "__v": 0
      }
    ],
    "__v": 1,
    "itemsToPost": [
      {
        "title": "Aριθμ.: Υ194/12.6.2015 Τροποποίηση απόφασης ανάθεσης αρμοδιοτήτων στον Αναπληρωτή Υπουργό Οικονομικών Δημήτριο Μάρδα.",
        "summary": "Τροποποίηση απόφασης ανάθεσης αρμοδιοτήτων στον Αναπληρωτή Υπουργό Οικονομικών Δημήτριο Μάρδα.",
        "url": "http://www.taxheaven.gr/laws/circular/view/id/21113",
        "published_at": 1434056400000,
        "time_ago": "5 days ago",
        "guid": {
          "link": "http://www.taxheaven.gr/laws/circular/view/id/21113",
          "isPermaLink": "true"
        }
      }
    ]
  },
  {
    "_id": "558093013106203517f96d9c",
    "name": "",
    "imageUrl": "",
    "url": "http://www.taxheaven.gr/bibliothiki/soft/xml/soft_new.xml",
    "latestDate": "1434489601236",
    "endpoints": [],
    "__v": 0,
    "itemsToPost": [
      {
        "title": "Taxheaven - Άμεση ενημέρωση - Έγκαιρη επιστημονική κωδικοποίηση - Καινοτομικά εργαλεία. Κωδικοποιήθηκαν όλοι οι νόμοι στους οποίους επιφέρει αλλαγές ο νόμος 4330/2015",
        "summary": {},
        "url": "http://www.taxheaven.gr/news/news/view/id/24088",
        "published_at": 1434494400000,
        "time_ago": "about 4 hours ago",
        "guid": {
          "link": "http://www.taxheaven.gr/news/news/view/id/24088",
          "isPermaLink": "true"
        }
      }
    ]
  }
]
12
  • I'm using async at the moment for the waterfall pattern. Do you know which method roughly covers this functionality? I may just have to dive in and see what I can find Commented Jun 17, 2015 at 3:20
  • Wait.. those aren't asynchronous loops? That's ridiculous. You should turn the .forEach into for loops so you can exit the function as soon as you call the callback. Commented Jun 17, 2015 at 3:20
  • @PatrickRoberts I'm not sure that library applies to this situation. It looks like he is drilling down into nested data synchronously. It's just that his countSuccessPosts function is called asychronously. At least that's how I'm reading it. It'd help if we had some sample data. Commented Jun 17, 2015 at 3:21
  • Yes the only asynchronous call is countSuccessPosts in the waterfall, I am basically just drilling into nested data here, I just have to repeat this pattern often and imagine that there's a more optimized route to take. Commented Jun 17, 2015 at 3:24
  • Could you possibly provide a JSON.stringify(rssFeeds, null, 2) to the question, or at least a portion of it to help clarify the format of the object? Commented Jun 17, 2015 at 3:26

3 Answers 3

2

I didn't check this but it is pretty similar to what I'm currently using in my project:

function countSuccessPosts(rssFeeds, cb){
  async.each(rssFeeds, function(eachFeed, outerCallback) {
     async(eachFeed.itemToPost, function(eachItem, innerCallback) {
        if(item.response.id) {
            //Do Something That Is Actually Async. Could be asking the server for success flag, for instance.
            doSomethingThatIsActuallyAsync(item.response.id).then(function (err) {
                if (!err) {
                    successCounter = successCounter + 1;
                }
                innerCallback();
            });
        } else { //This could be to skip "empty responses" without the need to go to the server, right?
            innerCallback();
        }
     }, outerCallback);
  }, function() {
      //All done
      cb(null, rssFeeds, successCounter);
  }); 
}

As others mentioned, you need this only if you have actual async methods calls inside the inner loop.

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

4 Comments

Sorry to say but you're a little late. // some code was not async.
ouch, that's because I took to much time to prettify the answer, lol.
Don't worry, it happens.
@PatrickRoberts at least he added a postscript saying this is only necessary if there's async methods in the inner loop.
0

You don't need to keep track of the last item. Just call the callback after both loops exit. I also changed the .forEach to for loops as these execute faster.

function countSuccessPosts(rssFeeds, cb) {
  var index, itemIndex, feed, item;

  for (index = 0; index < rssFeeds.length; index++) {
    feed = rssFeeds[index];

    for (itemIndex = 0; itemIndex < feed.itemsToPost.length; itemIndex++) {
      item = feed.itemsToPost[itemIndex];

      if(item.response && item.response.id) {
        successCounter++;
      }
    }
  }

  cb(null, rssFeeds, successCounter);
}

Of course, if you'd rather call countSuccessPosts without a callback the calling code can look like:

var successPosts = countSuccessPosts(rssFeeds);

And you can reformat the function to look like this:

function countSuccessPosts(rssFeeds) {
  var index, itemIndex, feed, item, successCounter = 0;

  for (index = 0; index < rssFeeds.length; index++) {
    feed = rssFeeds[index];

    for (itemIndex = 0; itemIndex < feed.itemsToPost.length; itemIndex++) {
      item = feed.itemsToPost[itemIndex];

      if(item.response && item.response.id) {
        successCounter++;
      }
    }
  }

  return successCounter;
}

7 Comments

A) There's no need to use a callback when rssFeeds can be accessed synchronously. B) Otherwise, If // some code has async calls, your code breaks anyway because cb is called before the async function would finish.
@naomik it is possible he's using async simply because the syntax helps organize and simplify his already pyramid-like code.
Well it's bad to write async control around synchronous code. Don't encourage this. JavaScript is single-threaded. Adding a callback to your function doesn't change this.
Yeah that's basically it. I'm just looking for a better pattern for looping through this data, since I have to do it relatively often, as I'm passing this array through the waterfall pattern and mutating it along the way.
@mrmayfield I added a quick edit to ensure that item.response.id doesn't throw a reference error if item.response is undefined.
|
0

Wait, why are you using a callback when you can read the data synchronously?

After you've updated your question, it looks like you're just summing a number of elements in an array

Here's a fully synchronous version that counts the number of itemsToPost that have a valid response.id set.

function countSuccessPosts(rssFeeds) {
  return rssFeeds.reduce(function(sum, x) {
    return sum + x.itemsToPost.filter(function(y) {
      return !!y.response.id;
    }).length;
  }, 0);
}

If you're required to inject this into an async control flow, you can easily put a wrapper on it

function(rssFeeds, done) {
  done(null, rssFeeds, countSuccessPosts(rssFeeds));
}

The point tho, is that countSuccessPosts has a synchronous API because everything that happens within that function is synchronous.

5 Comments

Sure your code is shorter, but it runs slower than my solution, and is harder to read.
The reason is that I'm splitting the functionality out into small methods, many of which rely on async (database and network API calls), and organizing the code using the async waterfall pattern and passing through the rssFeeds array.
Hey, I'm just looking to learn. I wouldn't doubt it at all if my design was unoptimized. If you have a suggestion for how to better organize my code given my explanation above of my needs I would be happy to hear it!
@mrmayfield I provided the synchronous function and the async wrapper to use in your waterfall control code. Think about it tho: just because you're counting the posts in an async portion of code, doesn't mean that the count is async. What happens if you want to call the count function in a different part of your code? With the async-only api, it would seem kind of broken because it's forcing the callback even tho it's completely synchronous. Keep the functions intent separate from the control flow and just use the wrapper where necessary.
Yeah that's a very good point. I see what you're saying and will definitely keep an eye out for my sychronous-only methods and try to keep them out of my asynchronous workflow. Thanks for the help!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.