Skip to content

Instantly share code, notes, and snippets.

@peterc
Last active August 28, 2018 20:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save peterc/a7d63960ca6dcf165477b310d0d08afb to your computer and use it in GitHub Desktop.
Save peterc/a7d63960ca6dcf165477b310d0d08afb to your computer and use it in GitHub Desktop.
An ES6 variant of a serverless function written in Python..
const fetch = require('node-fetch');
const parseString = require('xml2js').parseString;
const util = require('util');
const hostnames = ["react.statuscode.com", "javascriptweekly.com", "nodeweekly.com"];
function checkValidity(url) {
return fetch(url)
.then(res => res.text())
.then(resultText => promisify(parseString)(resultText))
.then(res => res['env:Envelope']['env:Body'][0]['m:feedvalidationresponse'][0]['m:validity'][0]);
}
async function postSlackMessage(message, channel = '#experiments') {
let slackData = { text: message, channel: channel };
return fetch(process.env['SLACK_URL'], { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(slackData) })
}
async function lambda_handler(event, context) {
for (let hostname of hostnames) {
let url = `https://validator.w3.org/feed/check.cgi?url=https%3A%2F%2F${hostname}%2Frss&output=soap12`;
if (await checkValidity(url)) {
await postSlackMessage(`:white_check_mark: ${hostname} has valid RSS`);
} else {
await postSlackMessage(`:x: ${hostname} has INVALID RSS`, '#editorial');
}
}
}
if (process.argv[2] == 'test') {
lambda_handler(null, null);
}
@JonAbrams
Copy link

JonAbrams commented Aug 28, 2018

My attempt at simplifying checkValidity

const util = require('util');
async function checkValidity(url) {
  const resultText = await fetch(url).then(res => res.text());
  const parsedResult = await util.promisify(parseString)(resultText);
  return parsedResult['env:Envelope']['env:Body'][0]['m:feedvalidationresponse'][0]['m:validity'][0];
}

And this allows throwing of errors 😄

@peterc
Copy link
Author

peterc commented Aug 28, 2018

Was not aware of promisify, will take a look! Thanks! :)

@peterc
Copy link
Author

peterc commented Aug 28, 2018

That didn't work as-is, but this did:

async function checkValidity(url) {
  return fetch(url)
           .then(res => { return res.text() })
           .then(resultText => util.promisify(parseString)(resultText))
           .then(res => res['env:Envelope']['env:Body'][0]['m:feedvalidationresponse'][0]['m:validity'][0]);
}

@rauschma
Copy link

A few tips:

res => { return res.text() } // no
res => res.text() // yes

const parseString = require('xml2js').parseString; // no
const {parseString} = require('xml2js'); // yes

I’d rewrite:

const {promisify} = require('util');
const parseStringAsync = promisify(parseString);

async function checkValidity(url) {
  const res = await fetch(url);
  const resultText = await res.text();
  const parsed = await parseStringAsync(resultText);
  return parsed['env:Envelope']['env:Body'][0]['m:feedvalidationresponse'][0]['m:validity'][0];
}

If you do the promisifying yourself, you should not use an async function as the callback and you should handle errors:

function parseStringAsync(str) {
  return new Promise((resolve, reject) => {
    parseString(str, (err, result) => {
      if (err) {
        reject(err);
      } else {
        resolve(result);
      }
    })
  });
}

@peterc
Copy link
Author

peterc commented Aug 28, 2018

Aha, now let me look at your updated version, as it does look nicer than the chain of thens!

@vkarpov15
Copy link

Also, just as a warning, don't do this:

parseString(resultText, async function(err, result) {
  res(result['env:Envelope']['env:Body'][0]['m:feedvalidationresponse'][0]['m:validity'][0]);
})

In general, you shouldn't mark a callback as async because then calling the callback creates a promise that is never used.

@peterc
Copy link
Author

peterc commented Aug 28, 2018

Thanks for the tip @vkarpov15

Also, a total cheat version partly inspired by Jon :-D

async function checkValidity(url) {
  let res = await fetch(url).then(res => res.text());
  return res.match("<m:validity>true");
}

The XML will remain consistently formatted.. right?

@yevhenpavliuk
Copy link

const checkValidity = url =>
  fetch(url)
    .then(response => response.text())
    .then(text => text.includes("<m:validity>true"));

Although checking if the XML contains a substring may be enough in this particular case, I would not rely on it.

@ThisIsMissEm
Copy link

ThisIsMissEm commented Aug 28, 2018

Try:

function checkValidity(url) {
  const res = await fetch(url)
  const resultText = await res.text()
  
  const parsed = await promisify(parseString)(resultText))

  return parsed['env:Envelope']['env:Body'][0]['m:feedvalidationresponse'][0]['m:validity'][0];
}

@peterc
Copy link
Author

peterc commented Aug 28, 2018

Thanks for the help folks! Here's where I'm at so far, since this is so scrappy:

const fetch = require('node-fetch');

const hostnames = ["react.statuscode.com", "javascriptweekly.com", "nodeweekly.com"];

async function checkValidity(url) {
  const res = await fetch(url).then(res => res.text());
  return res.match("<m:validity>true");
}

async function postSlackMessage(message, channel) {
  const slackData = { text: message, channel: channel };
  await fetch(process.env['SLACK_URL'], { method: 'POST', body: JSON.stringify(slackData) })
}

async function doValidity(hostname) {
  const url = `https://validator.w3.org/feed/check.cgi?url=https%3A%2F%2F${hostname}%2Frss&output=soap12`;

  if (checkValidity(url)) {
    postSlackMessage(`:white_check_mark: ${hostname} has valid RSS (JS)`, '#experiments');
  } else {
    postSlackMessage(`:x: ${hostname} has INVALID RSS (JS)`, '#editorial');
  }
}

exports.lambdaHandler = (event, context, callback) => {
  hostnames.map(hostname => doValidity(hostname));
  if (callback) callback(null, "success");
};

if (process.argv[2] == 'test') exports.lambdaHandler();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment