Skip to content

Instantly share code, notes, and snippets.

@yeraydiazdiaz
Created September 28, 2017 14:07
Show Gist options
  • Save yeraydiazdiaz/a94aa3b341f1946a84f4a47ac3715555 to your computer and use it in GitHub Desktop.
Save yeraydiazdiaz/a94aa3b341f1946a84f4a47ac3715555 to your computer and use it in GitHub Desktop.
A port of the recursive HN comment calculator used in "Asyncio Coroutine Patterns" using Node.js async/await
/**
* A port of the recursive HN comment calculator used in
* "Asyncio Coroutine Patterns" using Node.js async/await
*
* Assume we want to calculate the number of comments of a particular post in
* Hacker News by recursively aggregating the number of descendants.
*
* Original article at:
* https://medium.com/python-pandemonium/asyncio-coroutine-patterns-beyond-await-a6121486656f
*
* And Python code:
* https://github.com/yeraydiazdiaz/asyncio-coroutine-patterns/blob/master/01_recursive_coroutines/recursive_coroutines.py
*/
const url = require('url')
const axios = require('axios')
const winston = require('winston')
const ArgumentParser = require('argparse').ArgumentParser
const instance = axios.create({
baseURL: 'https://hacker-news.firebaseio.com/v0/item/',
timeout: 5000,
})
/**
* Fetches a single post by ID.
* @param {number} id - The post's ID.
* @return {object} The `data` of the Axios response.
*/
async function fetchPost(id) {
const start = new Date()
const response = await instance.get(`${id}.json`)
const end = new Date()
winston.debug(
`Fetching post with ID ${id} took ${(end - start) / 1000} seconds`)
return response.data
}
/**
* Calculates the number of comments for a post
* @param {number} id - The post's ID.
* @return {number} The number of comments for the post.
*/
async function getNumberOfComments(id) {
const response = await fetchPost(id)
if (!response.hasOwnProperty('kids')) return 0
let numberOfComments = response.kids.length
const results = await Promise.all(response.kids.map(getNumberOfComments))
numberOfComments += results.reduce((sum, value) => sum + value)
winston.debug(`${id} > ${numberOfComments} comments`)
return numberOfComments
}
/**
* Gets the post ID from a URL
* @param {string} URL - The post's URL.
* @return {int} The post's ID.
*/
function getIDFromURL(URL) {
const id = url.parse(URL).query.slice(3)
return parseInt(id)
}
/**
* Entry point to the script when ran directly in the console
* @param {object} args - Arguments from `argparse`.
*/
function cli() {
const parser = new ArgumentParser({
addHelp: true,
description: 'Recursive HN comment calculator using Node.js async/await',
})
parser.addArgument('--id', {
help: 'ID of the post in HN, defaults to 8863',
type: 'int',
defaultValue: 8863,
})
parser.addArgument('--url', {
help: 'URL of a post in HN, use instead of `id`',
type: 'string',
})
parser.addArgument('-v', {
help: 'Verbose output`',
action: 'storeTrue',
})
const args = parser.parseArgs()
if (!args.id && !args.url) {
throw new Error('Too few arguments, please use either `id` or `url`')
}
winston.level = args.v ? 'debug' : 'info'
const id = args.url ? getIDFromURL(args.url) : args.id
winston.info(`Calculating comments for post ${id}`)
const start = new Date()
getNumberOfComments(id)
.then((res) => {
const elapsed = (new Date().getTime() - start.getTime()) / 1000
winston.info(`Number of comments: ${res}, took: ${elapsed} seconds`)
})
.catch((error) => winston.error(error))
}
if (require.main.id === 'module') {
exports.default = getNumberOfComments
} else {
cli()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment