0

I have a nested for loops Nodejs code, when I print out the result, the order is messed up, and I also got the UnhandledPromiseRejectionWarning due to async

#!/usr/bin/env node
'use strict';

let Parser = require('rss-parser');
let parserrss = new Parser();

const regions = ['us-east-1', 'us-west-2']
const services = ['s3', 'elasticache', 'management-console', 'cloudwatch', 'ec2', 'elb', 'rds', 'route53', 'vpc', 'iam',]

for(let i=0; i < regions.length; i++){
    for(let j=0; j<services.length; j++){

        let region = regions[i]
        let service = services[j]

        if (region == '' || service =='management-console' || service == 'route53'){
            var feed = service
        }
        else{
            var feed = service + '-' + region
        }

        const url = `http://status.aws.amazon.com/rss/${feed}.rss`
        //console.log(url)

        parserrss.parseURL(url).then(d => {

            const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun","Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
            const date = new Date()
            const month = monthNames[date.getMonth()]

            if(date.getDate().toString().length==1){
                var day = "0" + date.getDate().toString()
            } else {
                var day = date.getDate().toString()
            }

            const year = date.getFullYear().toString()


            if(d.items && d.items[0]["pubDate"].substring(5, 16)==`${day} ${month} ${year}`){
                console.log(`${region}-${services} event title: ${d.items[0]["title"]}`)
                console.log(`${region}-${service} event date: ${d.items[0]["pubDate"]}`)
                console.log(`${region}-${service} event description: ${d.items[0]["contentSnippet"]}`)
            }
            else if (d.title){
                console.log(`${region}-${service} status OK: No events to display`)
            }

        })

    }
}

This is the output, the order is not right, also there are 2 records are not what I am expected, which are indicated by the red mark

enter image description here

How can I fix it to make it print the correct order and records? I know I have to use aysnc/await function. actually I implemented it with async and callbacks. However, I am get the same issue. I am new to Nodejs and async world, no idea how to implement it in my code.

Ayush Gupta
  • 8,716
  • 8
  • 59
  • 92
Akira
  • 273
  • 5
  • 15

2 Answers2

1

kk so a few things to go over.

When working with async loops, you want to consider what async truly means here.

If you loop over each item one by one and execute them, though they are async requests, each request waits on the previous which makes the flow sync.

This means your application is much, much slower then if all requests were fired as soon as they could be.

The approach you should consider here is using map with Promise.all to build out an object you can then order and print after all the requests are resolved.

That will ensure all requests are execute as fast as possible but the printed order is still preserved.

With that aside, here are some updates, using your for loop approach, that fires each request in order (i moved some const and such out but there is still cleanup needed):

let Parser = require('rss-parser');
let parserrss = new Parser();

const regions = ['us-east-1', 'us-west-2']
const services = ['s3', 'elasticache', 'management-console', 'cloudwatch', 'ec2', 'elb', 'rds', 'route53', 'vpc', 'iam',]

const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
const date = new Date()
const month = monthNames[date.getMonth()]
const dateString = date.getDate().toString()
const day = dateString.length === 1 ? `0${dateString}` : dateString;
const year = date.getFullYear().toString()

const formatServiceRegion = (service, region) =>
  (region == '' || service =='management-console' || service == 'route53') ? service : `${service}-${region}`

const parsedRssFeeds = async (regions, services) => {
  for(let ii = 0; ii < regions.length; ii += 1) {
    const region = regions[ii];

    for (let jj = 0; jj < services.length; jj += 1) {
      const service = services[jj];

      const feed = formatServiceRegion(service, region)
      const url = `http://status.aws.amazon.com/rss/${feed}.rss`

      let title;
      let items;

      try {
        ({ title, items } = await parserrss.parseURL(url))
      } catch (_) {
        continue
      }

      if (items && items[0]["pubDate"].substring(5, 16) ===`${day} ${month} ${year}`) {
          console.log(`${region}-${services} event title: ${items[0]["title"]}`)
          console.log(`${region}-${service} event date: ${items[0]["pubDate"]}`)
          console.log(`${region}-${service} event description: ${items[0]["contentSnippet"]}`)
      } else if (title) {
          console.log(`${region}-${service} status OK: No events to display`)
      }
    }
  }
}

parsedRssFeeds(regions, services)
Dreamlines
  • 380
  • 4
  • 15
0

UnhandledPromiseRejectionWarning due to async this is because you are not using .catch method where .then method is getting finished.

 parserrss.parseURL(url).then(d => {
        }).catch(err => {
            //deal with error
         });

For the order is messed up I would say please move lines of codes where you are calling 3rd party method into new function and return Promise from it. After that you can use new method with .then or async/await to call and get result in expected order.

This is an example below without calling an API but returning URL in same order.

const regions = ['us-east-1', 'us-west-2']
const services = ['s3', 'elasticache', 'management-console', 'cloudwatch', 'ec2', 'elb', 'rds', 'route53', 'vpc', 'iam',];

let test = async (service, region) => {
    if (region == '' || service =='management-console' || service == 'route53'){
            var feed = service
        }
        else{
            var feed = service + '-' + region
        };
      const url = `http://status.aws.amazon.com/rss/${feed}.rss`

try {  
  //call here with await
  return Promise.resolve(url)     ;
}
    catch(e) {
      console.log("err in method");
      //return Promise.reject(e);
    }


};

regions.forEach((region) => {
  services.forEach(async (service) => {
    try{
   let res = await test(service, region);
   console.log(res);
    }catch(e) {
      //rejection comes here
      console.log("erro rin calling");
    }
  })
});
Garry
  • 536
  • 1
  • 5
  • 11
  • the warning is gone by adding the catch method, However, my result is kinda messed up, I am missing a record, also there are additional 2 records show up which I don't want, `us-west-2-s3,elasticache,management-console,cloudwatch,ec2,elb,rds,route53,vpc,iam`. Could you provide me some example how to fix the order thing? Thanks – Akira Jun 06 '19 at 22:43
  • Thanks for the solution first. Actually I am able to get the correct order for the `url` in the original code. However, when i use the `rss-parser` module to parse the url, the output order messed up and there are two records are wrong, which is mentioned in the comment above. – Akira Jun 07 '19 at 15:52
  • Using `async` inside of `forEach` is not going to produce the behavior you're expecting: https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop either going to want `map` with `Promise.all` or a classic `for` loop – Dreamlines Jun 07 '19 at 15:58