0

In an async IIFE at the bottom of this javascript, you'll see that I'm trying to: 1) read a JSON file, 2) get multiple RSS feed URLs from that data, 3) pull and parse the data from those feeds, and create an object with that data, so I can 4) write that pulled RSS data object to a JSON file. Everything for #1 and #2 is fine. I'm able to pull data from multiple RSS feeds in #3 (and log it to console), and I'm comfortable handling #4 when I get to that point later.

My problem is that, at the end of step #3, within the const parseFeed function, I am trying to create and push an object for that iteration of rssJSONValsArr.map() in the IIFE and it's not working. The rssFeedDataArr result is empty. Even though I am able to console.log those values, I can't create and push the new object I need in order to reach step #4. My creating of a similar object in #2 works fine, so I think it's the map I have to use within const parseFeed to pull the RSS data (using the rss-parser npm package) which is making object creation not work in step #3. How do I get rssFeedOject to work with the map data?

import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import Parser from 'rss-parser';
const parser = new Parser();

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const feedsJSON = path.join(__dirname, 'rss-feeds-test.json');

const rssJSONValsArr = [];
const rssFeedDataArr = [];

const pullValues = (feedObject, i) => {
  const url = feedObject.feed.url;
  const jsonValsObject = {
    url: url,
  };
  rssJSONValsArr.push(jsonValsObject);
};

const parseFeed = async (url) => {
  try {
    const feed = await parser.parseURL(url);

    feed.items.forEach((item) => {
      console.log(`title: ${item.title}`); // correct
    });

    const rssFeedOject = {
      title: item.title,
    };
    rssFeedDataArr.push(rssFeedOject);
  } catch (err) {
    console.log(`parseFeed() ERROR : ${err}`);
  }
};

(async () => {
  try {
    console.log('1: read feeds JSON file');
    const feedsFileArr = await fs.promises.readFile(feedsJSON, {
      encoding: 'utf-8',
    });
    const jsonObj = JSON.parse(feedsFileArr);

    console.log('2: get feed URLs');
    jsonObj.slice(0, 30).map(async (feedObject, i) => {
      await pullValues(feedObject, i);
    });
    console.log('rssJSONValsArr: ', rssJSONValsArr); // correct

    console.log('3: pull data from rss feeds');
    rssJSONValsArr.map(async (feedItem, i) => {
      await parseFeed(feedItem.url, i);
    });
    console.log('rssFeedDataArr: ', rssFeedDataArr); // empty !!!

    // console.log('4: write rss data to JSON file');
    // await fs.promises.writeFile(
    //   `${__dirname}/rss-bulk.json`,
    //   JSON.stringify(rssFeedDataArr)
    // );

    console.log('5: Done!');
  } catch (err) {
    console.log(`IIFE CATCH ERROR : ${err}`);
  }
})();

Example JSON file with two RSS feed URLs:

[
  {
    "feed": {
      "details": {
        "name": "nodejs"
      },
      "url": "https://news.google.com/rss/search?q=nodejs"
    }
  },
  {
    "feed": {
      "details": {
        "name": "rss-parser"
      },
      "url": "https://news.google.com/rss/search?q=rss-parser"
    }
  }
]

Any and all help appreciated. Thanks

david
  • 243
  • 3
  • 17
  • 1
    Why use `map` if you're not mapping any values? Just use a `forEach` or `for` in those cases. – Reyno Jan 24 '23 at 12:12
  • 2
    Does this answer your question? [Using async/await with a forEach loop](https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop) (Yes, you're (incorrectly) using `.map()` instead of `.forEach()` here, but they behave the same in this case.) – Ivar Jan 24 '23 at 12:13
  • Those suggested links aren't helpful, I don't think. The issue isn't using async with a loop (I did that already with `const pullValues` here, which is called by an async `map`). It's iteratively creating new objects that I can't figure out. I changed the `map` to a `forEach`, but that doesn't solve my problem (just tried it). Thanks – david Jan 24 '23 at 12:20
  • @david Your `pullValues` is fully synchronous, which is why this issue doesn't happen there. And I'm aware that `.forEach()` doesn't work. That's what the post I linked is all about. – Ivar Jan 24 '23 at 12:24

1 Answers1

1

The problem is you are printing rssFeedDataArr right after the .map call, which, like stated on the comments, is being incorrectly used, since you are not using the returned value, forEach would be the way to go here. For every value in rssJSONValsArr you are calling an anonymous and async function which in turn awaits for parseFeed, so you are basically creating a Promise in each iteration, but obviously those promises are resolved after your print statement is executed. You need to wait for all of those promises to be resolved before printing rssFeedDataArr. One way to do that, since you are creating a bunch of promises which can be run in parallel is to use Promise.all, like this:

await Promise.all(
  rssJSONValsArr.map(async (feedItem, i) => {
    await parseFeed(feedItem.url, i);
  });
)

and you we can simplify it even more and return the promise created by parseFeed directly:

await Promise.all(
  rssJSONValsArr.map((feedItem, i) => parseFeed(feedItem.url, i))
)

And in this case the right method is map and not forEach

In the case of rssJSONValsArr it works because the call to pullValues is being resolved instantly, it doesnt run asynchronously, even when its declared as async, there is not await inside the function definition.

JoshuaCS
  • 2,524
  • 1
  • 13
  • 16
  • 1
    This is basically what is already explained [here](https://stackoverflow.com/a/37576787). Please [don't duplicate content on Stack Overflow](https://stackoverflow.com/help/duplicates). If an answer already exists, flag/vote to close it as a duplicate. – Ivar Jan 24 '23 at 12:30
  • @Ivar Not sure why this had to be shut down so quickly. The link you keep posting overlaps here, but doesn't answer my question. Besides, "duplication is not necessarily bad. Quite the contrary — some duplication is desirable. There’s often benefit to having multiple subtle variants of a question around" https://stackoverflow.blog/2010/11/16/dr-strangedupe-or-how-i-learned-to-stop-worrying-and-love-duplication/ – david Jan 24 '23 at 12:51
  • _Some_ duplication sure can be desirable, but rewriting an existing answer most certainly is not. Stack Overflow is first and foremost a database of questions and answers aimed for future visitors to benefit from in case they encounter the same issue. How do you expect future visitors to benefit from this answer over the already existing one? And if in the future the way this is handled in JavaScript changes, who is going to update this post? We close questions as duplicate so all the relevant information can stay in one place. – Ivar Jan 24 '23 at 13:03
  • "_Not sure why this had to be shut down so quickly_" - Not entirely sure what you are referring to. If you mean closing the question as a duplicate, than by Stack Overflow's standards its not quick at all. Questions like these are usually closed within minutes to _prevent_ answers from being added. If you're referring to my comment on this question, then I'm not sure what you expected me to do. Wait for a few hours before adding it? I'd argue that it is useful to know how Stack Overflow works so you get the chance to adjust to it, instead of being downvoted into oblivion. – Ivar Jan 24 '23 at 13:05
  • 1
    It's true there is some overlap between this question and the ones linked, but the answers there neither help nor clarify another problem the OP has regarding why two similar codes work in one case and not the other, so this question IS NOT A DUPLICATE of the linked posts and it should be reopened. – JoshuaCS Jan 24 '23 at 13:16
  • @Ivar can you tell me where that explanation is given in the links you provided? – JoshuaCS Jan 24 '23 at 13:25
  • 1
    @david in the meantime, mark my answer as the correct one (if you can) if it fixed your problem, if not, let me know what went wrong. Thanks. – JoshuaCS Jan 24 '23 at 13:27
  • You mean [the explanation that you added](https://stackoverflow.com/posts/75221493/revisions) _after_ I posted my comment? Your initial revision doesn't cover anything new over the answer I linked other than the variable names. In your first sentence you correctly mention that `.map()` is being misused here, but that on its own doesn't break anything, so that's not really an answer to the problem OP is facing. But even after the edit it doesn't really make it not a duplicate. The problem OP is facing is the fact that they use async/await inside of a `.forEach()` (or `.map()` in their case). – Ivar Jan 24 '23 at 13:35
  • Actually, I edited the post right after submitting the original version and before reading your comment. Yes, one issue here is the use of async/await inside forEach/map, but you are conveniently jumping over the other issue I mention. My answer provides real value and tells the OP why the first map works, something the posts you point to do not. In fact, without my explanation, he would have read those answers and wouldn't still know whats going on with the mapping in step #2. – JoshuaCS Jan 24 '23 at 13:53
  • @JoshuaCS Thank you for your help here. My problem was two-fold. Yes, I needed a Promise.all (and didn't need the async inside of the `rssJSONValsArr.map` as your simplified answer showed). Also, the object creation and array push needed to be inside of `feed.items.forEach` (which works as a `map` or `forEach`). I had actually tried both of those variations before coming on SO to ask, but apparently not at the same time, because together they work. I'm able to create and output my array of objects. – david Jan 24 '23 at 20:52
  • @JoshuaCS My only problem now is that my process is not exiting after the "5: Done!" log. I have to manually quit it in the terminal. I'm not sure why that is. – david Jan 24 '23 at 20:58
  • @Ivar Normally, if I got a good answer that didn't quite fully answer my question, I'd accept that answer and post a supplementary answer with the full solution when I figured it out, which I think adds benefit for other users, but what's the point here? No one will see it. Personally, I'd rather see SO address the countless outdated solutions still showing up in search engines, things that are 10+ years old, predate ES6, etc. But, I'm guessing the web traffic from those old old posts is considered too valuable to add a no-index or something to remove those outdated search listings. – david Jan 24 '23 at 21:46
  • That'd be nice yes. There is the [Outdated Answers project](https://meta.stackoverflow.com/questions/405302/introducing-outdated-answers-project) which aims to address this. But [the last update](https://meta.stackoverflow.com/questions/415738/outdated-answers-up-next-changes-to-sorting-menu) was almost a year ago, so I'm not sure if they still work on it. (And of course it would reduce the workload if the same outdated answers weren't duplicated everywhere. ;-)) – Ivar Jan 24 '23 at 22:32