JavaScript - learn 80% of a new language in a week

JavaScript - learn 80% of a new language in a week

We’re continuing our series of articles where we tak 12 relatively simple coding assignments to learn 80% of a new language in a week.

Code samples below provide similar or identical functionality as [TODO - add link to ruby here] ruby snippets in my previous article.

Working with Lists - Wrangling data efficiently
const result = Array.from({ length: 6 }, (_, i) => i + 97) // Getting numbers 97 to 102
  .filter(num => num % 2 !== 0) // Keeping only the odd numbers πŸš«πŸ‘«
  .map(num => String.fromCharCode(num)) // Converting numbers to characters (ASCII magic) ✨
  .sort((a, b) => /[aeiouy]/.test(a) ? -1 : 1) // Vowels get a fast pass 🎒
  .map(chr => chr.toUpperCase()) // Making them shout! πŸ”Š
  .slice(0,2) // Taking the first two characters after the reverse
  .join(''); // Joining them to form our final string
  
console.log(result) // "EA"

Alright, let’s dive into this JavaScript snippet and break it down, casual style with a sprinkle of emoji. πŸ•ΆοΈβœ¨

We start with a mission: to grab some characters, do a bit of sorting magic, make them loud (uppercase), flip ’em, and then showcase our final duo. Let’s roll:

  1. Gathering the Squad πŸŽ’: Array.from({ length: 6 }, (_, i) => i + 97) is our cool way of rounding up numbers 97 to 102. It’s like calling all kids born in the late ’90s to line up!

  2. Odd Ones, Front and Center πŸš€: .filter(num => num % 2 !== 0) picks out the odd numbers because today, it’s all about celebrating uniqueness. It’s like choosing only the odd socks from your drawer.

  3. From Numbers to Letters 🌈: .map(num => String.fromCharCode(num)) transforms those numbers into characters. Imagine turning numbers into letters with a magic wand. Poof! Now we have ‘a’, ‘c’, ’e’.

  4. Vowel VIPs 🎀: .sort((a, b) => /[aeiouy]/.test(a) ? -1 : 1) is where vowels get the VIP treatment and move to the front. It’s like letting the vowels cut in line at a concert.

  5. Shout It Out πŸ”Š: .map(chr => chr.toUpperCase()) takes our characters and turns up the volume to uppercase. It’s like telling them to speak up at a noisy party.

  6. Flip It πŸ”„: .reverse() simply reverses the order. Imagine walking backward just for the fun of itβ€”that’s what we’re doing here.

  7. Top Two πŸ₯‡: .slice(0, 2) grabs the first two characters after the reverse, which lands us with ‘E’ and ‘A’. It’s like picking the first two winners in a race.

  8. Join the Party πŸŽ‰: .join('') brings our characters together for the final show, presenting “EA” as our grand masterpiece.

And there we have it, “EA” emerges, shining in uppercase after our coding adventure. 🌟

Ruby’s approach to sorting, with its stable sort algorithm, contrasts with JavaScript’s. Imagine Ruby and JavaScript as chefs with distinct recipes for arranging elements. Ruby ensures items that compare equally keep their original order, providing predictability. This highlights how programming languages, like chefs, have their unique styles, particularly in their sorting methods. 🍳🎈

JSON/XML/YAML Mapping - Converting between formats and objects

Shifting gears to JavaScript and Node.js for a fun dive into working with XML and JSON! Here’s a casual walkthrough of a Node.js script that plays around with XML and JSON like a champ. First up, we’ll whip up some XML, transform it into a JavaScript object, sprinkle in a few tweaks, and finally, convert it to JSON. All in one slick move:

First things first, Node.js doesn’t have a built-in way to parse XML like Ruby does with Nokogiri, but we can use the xml2js package, which is pretty much the Swiss Army knife for XML in the Node.js world. Make sure you have xml2js installed by running npm install xml2js.

const xml2js = require('xml2js');
const parser = new xml2js.Parser();

// Step 1: Craft some XML
const xmlData = `
<friends>
  <friend>
    <name>Joey</name>
    <age>30</age>
  </friend>
  <friend>
    <name>Rachel</name>
    <age>29</age>
    <favoriteCoffee>Latte</favoriteCoffee>
  </friend>
</friends>
`;

// Step 2: Convert XML to a JavaScript Object
parser.parseString(xmlData, (err, result) => {
  if (err) {
    throw err; // Handling potential errors like a boss πŸ•ΆοΈ
  }
  
  // Our cozy JavaScript object
  const friendsObj = result.friends.friend.map(friend => {
    return {
      name: friend.name[0],
      age: parseInt(friend.age[0], 10),
      favoriteCoffee: friend.favoriteCoffee ? friend.favoriteCoffee[0] : undefined
    };
  });

  // Step 3: Time to make some edits
  friendsObj.forEach(friend => {
    friend.age += 1; // Happy Birthday! πŸŽ‰
    if (!friend.favoriteCoffee) {
      friend.favoriteCoffee = "Cappuccino"; // Defaulting to Cappuccino β˜•
    }
  });

  // Step 4: Convert our updated JavaScript Object to JSON
  const jsonOutput = JSON.stringify(friendsObj, null, 2);

  // Step 5: Show off our final masterpiece
  console.log(jsonOutput); // Ta-da! 🎩✨
});

Breakdown:

  1. Crafting XML: We kick off with a cozy XML tale about Joey and Rachel. πŸ“œ
  2. XML to JavaScript Object: We leverage xml2js to turn our XML into a friendly JavaScript object. It’s like decoding a secret message. 🌐
  3. Sprinkling Some Edits: We give everyone a year bump in age (because why not?), and make sure everyone has a declared coffee preference, with Cappuccino as the default. β˜•
  4. JavaScript Object to JSON: A simple JSON.stringify turns our object into stylish, web-ready JSON. πŸ’«
  5. The Grand Reveal: We log our JSON to the console, showcasing our friends in a brand new digital format, complete with the latest age updates and preferred coffees. πŸ–¨οΈ

And there you have it, a Node.js journey from XML to JSON, all while keeping the vibe relaxed and the code fun. Just remember, working with XML in Node.js might need a bit of setup with packages like xml2js, but it’s all part of the adventure. Happy coding! πŸš€βœ¨

Dictionary Reversal - Flipping keys and values

Alright, let’s spin this concept into JavaScript! We’re building a function that takes an object (JavaScript’s version of hashes or dictionaries) as input, flips its keys and values around, and navigates the tricky waters of non-unique values. Here’s our strategy:

function swapKeysAndValues(originalObj) {
  // Initialize a new object to store our swapped pairs.
  const swappedObj = {};

  // Loop through each key-value pair in the original object.
  Object.entries(originalObj).forEach(([key, value]) => {
    // If the value already exists as a key in our new object, we append the current key to its list.
    if (swappedObj.hasOwnProperty(value)) {
      swappedObj[value] = Array.isArray(swappedObj[value]) ? swappedObj[value] : [swappedObj[value]];
      swappedObj[value].push(key);
    } else {
      // If the value is unique, just set it as a key with the current key as its value.
      swappedObj[value] = key;
    }
  });

  return swappedObj;
}

// Let's give it a whirl with an example.
const exampleObj = { 'a': 1, 'b': 2, 'c': 1 };
console.log("Original:", exampleObj);
const swapped = swapKeysAndValues(exampleObj);
console.log("Swapped:", swapped);

Here’s the breakdown:

  • Initiate the swap: We begin with a brand new object to store our flipped key-value pairs. It’s like preparing a clean slate for our artwork.

  • Flip ’em: As we walk through the original object, we swap each key with its value. If our flipped key (originally a value) already exists in the new object, it’s party time! We turn it into an array and add the new key to the mix.

  • Dealing with Duplicates: Stumbling upon a value that’s already cozy as a key in our new object? No problem. We ensure it’s ready to accommodate multiple original keys by grouping them into an array.

  • Showtime: Testing it out with an example where ‘a’ and ‘c’ are both crushing on the number 1, we demonstrate our function’s slick handling of this quirky scenario.

And there you have it! We’ve got a JavaScript function flipping keys and values while smoothly dealing with those moments when values repeat themselves. πŸ”„βœ¨

JavaScript doesn’t have a built-in method like Ruby’s Hash.invert for a quick flip. However, this function shows how you can achieve similar functionality with a bit of custom code, tailored to handle even the non-unique value situations gracefully. So, if your object’s values are as unique as unicorns, consider yourself sailing smooth. But if you’re facing a duplicate value party, now you’ve got the tools to join in with style. πŸŽ‰πŸš€

Handling IO - Getting dirty

Shifting this Ruby script into the JavaScript world with Node.js, let’s keep the vibe light and add a twist of Node.js flavor! πŸš€ We’ll handle CSVs, make API calls, sprinkle in some randomness, and save our findings, all while keeping an eye on our progress. Node’s got us covered, but we might need to invite a few friends (libraries) to the party. Don’t worry; it’s all in the name of fun and functionality.

First up, make sure you’ve got csv-parser, axios (for those API calls), and fs (comes with Node, so no extra invites needed) ready to go. If csv-parser and axios aren’t hanging out in your project yet, just run:

npm install csv-parser axios json2csv

Now, let’s dive into the code:

/*
  Sample CSV content (save this as input.csv in your working directory):
  
  Name,Age,Email
  John Doe,28,john@example.com
  Jane Smith,22,jane@example.com
  Don Joe,33,donjoe@example.com
*/

const csv = require('csv-parser');
const fs = require('fs');
const axios = require('axios');
const { parse } = require('json2csv');

// Let's set the stage with our CSV file path
const inputFilePath = './input.csv';
const outputFilePath = './output.csv';

// Reads data from a CSV file and transforms it into an array of objects
function readCsvData(filePath) {
  return new Promise((resolve, reject) => {
    const results = [];
    fs.createReadStream(filePath)
      .pipe(csv())
      .on('data', (data) => results.push(data))
      .on('end', () => resolve(results))
      .on('error', reject);
  });
}

// Filters rows based on a condition
function filterData(rows, condition) {
  return rows.filter(condition);
}

// Fetches user data from an API for a given ID with a touch of randomness
async function fetchUserData(id) {
  try {
    const response = await axios.get(`https://jsonplaceholder.typicode.com/users/${id}`);
    // Adding a bit of suspense πŸ•°οΈ
    await new Promise(resolve => setTimeout(resolve, Math.random() * 1000));
    return response.data;
  } catch (error) {
    return { error: `Failed to fetch data for ID ${id}` };
  }
}

// Writes processed data to a new CSV file
async function writeToCsv(filename, data) {
  const csv = parse(data);
  fs.writeFileSync(filename, csv);
}

// The grand orchestra conductor, tying everything together
async function processCsv(inputFile, outputFile) {
  const data = await readCsvData(inputFile);
  const filteredData = filterData(data, row => parseInt(row.Age, 10) > 25);
  
  for (let i = 0; i < filteredData.length; i++) {
    const userData = await fetchUserData(i + 1);
    console.log(`Progress: ${i + 1}/${filteredData.length} rows processed.`);
    filteredData[i].APIResponse = JSON.stringify(userData);
  }
  
  await writeToCsv(outputFile, filteredData);
  console.log(`All data processed and saved to ${outputFile}.`);
}

// Example usage
processCsv(inputFilePath, outputFilePath);

What’s Different from Ruby to JS/Node.js? πŸ€”

  • Libraries Galore: Unlike Ruby’s standard libraries (like csv and net/http), we leaned on csv-parser and axios for CSV parsing and HTTP requests, respectively.

  • Promises and Async/Await: Node.js loves its asynchronous patterns, so we wrapped our CSV reading and API fetching in promises, ensuring smooth, non-blocking operations.

  • JSON Handling: Directly built into JavaScript, no need for a separate json library. Node.js treats JSON like the native it is.

  • Writing CSVs: We used json2csv for converting JSON back to CSV, showcasing Node.js’s flexible approach to data transformation.

And there you have itβ€”a fun, Node.js twist on a Ruby script, proving once again that while languages may differ in syntax and libraries, the end goal of crafting cool, functional code remains the same. Happy coding in whichever language you choose! πŸŽ‰βœ¨

Basic Project Setup - Hello World with options

Alrighty, let’s how to handle a basic script that will give us diffrent output depending on arguments we will provide. πŸš€ We’ll split our functionality across three files, just like in Ruby example, but keep in mind, the way we manage modules and import/export in Node.js differs a bit from Ruby’s require_relative. Let’s dive into the Node.js world with a touch of casual and emoji-powered explanations.

File 1: upperCase.js This file rocks the uppercase conversion.

// upperCase.js
module.exports = function upperCase() {
  console.log("HELLO, WORLD!");
}

File 2: lowerCase.js Here, we embrace the calmness of lowercase.

// lowerCase.js
module.exports = function lowerCase() {
  console.log("hello, world!");
}

File 3: helloWorld.js Our main star, deciding on the shout level based on command-line vibes.

// helloWorld.js
const upperCase = require('./upperCase');
const lowerCase = require('./lowerCase');

const arg = process.argv[2]; // Node.js uses process.argv to access command-line arguments

if(arg === "upper") {
  upperCase();
} else if(arg === "lower") {
  lowerCase();
} else {
  console.log("Hello, World!");
}

Cruising Through the Command Line πŸ•ΆοΈ To launch this cosmic greeting machine, navigate to your project’s directory in the terminal and prepare to be dazzled in one of three styles:

  1. Default Case (chills with “Hello, World!”):

    node helloWorld.js
    
  2. Uppercase (boldly shouts “HELLO, WORLD!”):

    node helloWorld.js upper
    
  3. Lowercase (whispers “hello, world!”):

    node helloWorld.js lower
    

Spot the Differences 🧐

  • Modules Galore: In Node.js, we use module.exports to share our functions across files, a bit different from Ruby’s require_relative but equally slick.
  • Command-Line Chats: Node.js taps into process.argv for command-line arguments, starting the count at 2 because the system reserves the first two slots for its own use (Node path and script path).

And just like that, you’ve translated Ruby vibes into Node.js magic, keeping the code modular, flexible, and ready for command-line fun. Dive in, experiment, and let the console be your playground! πŸš€βœ¨

AWS S3 Interaction - File operations in the cloud

Alright, let’s translate this Ruby script into a Node.js adventure, doing all sorts of fun stuff with AWS S3, like a photo (or file) moving from one album to another, getting a special cloud storage upgrade, and then making its way back, only to be chopped and squished into something more compact. We’ll be using aws-sdk for S3 gymnastics and zlib for that compression squeeze. First things first, let’s get our buddies installed:

npm install aws-sdk
npm install csv-writer

Note: Node.js has built-in support for zlib (compression) and reading/writing files, so no need for extra installs there!

Here’s how we’re gonna roll:

  1. Whipping up a CSV in memory: Imagine you’re in the kitchen, but instead of breadcrumbs, you’re sprinkling data.
  2. Uploading to S3: Picture posting that perfect selfie to the cloud.
  3. Album swap: Deciding it looks better in a different gallery.
  4. Storage upgrade: Like moving that selfie from a dusty album to a premium cloud spot.
  5. Retrieval: Sometimes, you just gotta have that photo back on your device.
  6. The chop and squish: Cutting up that photo into smaller bits and compressing them, making it easier to share or store.

Cooking the Code

const AWS = require('aws-sdk');
const fs = require('fs');
const zlib = require('zlib');
const createCsvWriter = require('csv-writer').createObjectCsvStringifier;

// Configure our AWS
AWS.config.update({ region: 'your-own-special-place' });
const s3 = new AWS.S3();

// Set the stage with bucket names and file details
const sourceBucket = 'your-original-album';
const destinationBucket = 'your-new-fancy-album';
const fileName = 'amazing_data.csv';
const objectKey = "secret-folder/" + fileName;

// 1. CSV Sandwich Making
const csvWriter = createCsvWriter({
  header: [
    {id: 'name', title: 'Name'},
    {id: 'age', title: 'Age'}
  ]
});

const data = [
  { name: "Alice", age: "30" },
  { name: "Bob", age: "25" }
];

const csvContent = csvWriter.stringifyRecords(data);

// 2. Upload Time
const uploadParams = {
  Bucket: sourceBucket, 
  Key: objectKey, 
  Body: csvContent
};

s3.putObject(uploadParams).promise()
  .then(() => console.log("Uploaded"))
  .catch(console.error);

// 3. Moving the File
const copyParams = {
  Bucket: destinationBucket,
  CopySource: `${sourceBucket}/${objectKey}`,
  Key: objectKey,
  StorageClass: 'GLACIER'
};

s3.copyObject(copyParams).promise()
  .then(() => console.log("File moved and storage class updated"))
  .catch(console.error);

// 5. Bringing it Back Home
const downloadParams = {
  Bucket: destinationBucket, 
  Key: objectKey
};

const fileStream = fs.createWriteStream(fileName);

s3.getObject(downloadParams).createReadStream()
  .pipe(fileStream)
  .on('finish', () => {
    // 6 & 7. Chopping and Squishing
    const readStream = fs.createReadStream(fileName);
    const writeStream = fs.createWriteStream(`${fileName}.gz`);
    const gzip = zlib.createGzip();
    
    readStream.pipe(gzip).pipe(writeStream).on('finish', () => {
      console.log("All done! Your file's been through quite the adventure.");
    });
  });

Differences to Note 🧐:

  • Modular Imports: Unlike Ruby’s gem system, Node uses require to import packages. AWS and CSV operations are handled by aws-sdk and csv-writer, respectively.
  • Async/Await Heaven: Node.js loves its promises and async/await pattern. It keeps our code clean and readable, especially when dealing with AWS’s asynchronous nature.
  • Built-in Goodies: fs for file operations and zlib for compression are part of Node’s standard library, so no extra installs are needed.

Just like that, you’ve translated Ruby’s elegant script into Node.js, ready to handle files with AWS S3 with grace and agility. Each step is a move in your data dance, from crafting and uploading CSVs to compressing and storing, all while navigating through Node.js’s async flows. Enjoy the ride! πŸš€βœ¨

OS Communication - Integrating with the host system

Alright, let’s turn this Ruby script into a JavaScript (Node.js) adventure, becoming command-line ninjas πŸ₯· with Node as our dojo. We’re about to run system commands and snag their outputs, all while keeping it cool and casual. Node.js doesn’t have an exact equivalent to Ruby’s Open3 library, but we can get pretty close with the child_process module, which is part of Node’s core libraries. No extra installations required here!

Getting our Ninja Gear Ready

In Node.js, the child_process module is our ninja toolkit. It’s packed with methods to execute system commands and play with their outputs. Let’s use exec, which suits our needs just fine.

Here’s how we set up our ninja moves:

const { exec } = require('child_process');

// Define a ninja move (function) to execute a command and capture its output
function executeCommand(cmd) {
  console.log(`Executing: ${cmd}`);
  exec(cmd, (error, stdout, stderr) => {
    if (error) {
      console.error(`Error:\n${stderr}`);
    } else {
      console.log(`Output:\n${stdout}`);
    }
  });
}

// Let's send our ninja on some missions

// List directory contents
executeCommand('ls -l');
// Example Output:
// Executing: ls -l
// Output:
// total 32
// drwxr-xr-x  5 user user 4096 Jul  7 10:00 app
// -rw-r--r--  1 user user  569 Jul  7 09:57 Gemfile

// Check disk usage
executeCommand('df -h');
// Example Output:
// Executing: df -h
// Output:
// Filesystem      Size  Used Avail Use% Mounted on
// /dev/sda1        50G   15G   33G  30% /

// Find CPU-hungry processes
executeCommand('ps aux --sort=-%cpu | head -5');
// Example Output:
// Executing: ps aux --sort=-%cpu | head -5
// Output:
// USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
// root      1588  7.5  0.1 247856 11944 ?        Ssl  09:14   0:01 /usr/lib/packagekit/packagekitd
// user     11768  6.5  2.5 1164032 205908 ?      Sl   09:15   0:03 /usr/bin/gnome-shell

Spot the Differences 🧐

  • No require_relative: In Node.js, we import modules with require. Since child_process is built-in, no npm install dance required.
  • Callbacks Over Synchronous Calls: Node.js loves non-blocking operations. exec uses a callback pattern, where we handle the output or error asynchronously, unlike Ruby’s more straightforward, synchronous capture3.
  • Surprise Order! 🎲: When our command-line ninja πŸ₯· starts running through its missions, don’t be too surprised if the outputs show up in a different order than you expected. Thanks to Node.js’s love for doing things asynchronously, all the system commands get called upfront, right as the script kicks into action. This means we’ll get a heads-up that all commands are executing almost at the same time, with each output appearing as soon as its respective command finishes. It’s like sending a bunch of texts and getting replies in a totally random order – you know everyone’s received the message, but who responds first is anyone’s guess!
  • Environment Familiarity: Just like in Ruby, knowing your environment is key. The commands ls -l, df -h, and ps aux are Unix-based. If you’re on Windows, you’ll need to tweak these to match Windows command line or PowerShell equivalents.

And there you have itβ€”a Node.js script ready to execute system commands with the agility of a ninja. Remember, with great power comes great responsibility, especially when executing system commands. Always double-check your commands to keep your digital dojo safe. Happy ninja-ing in Node.js! πŸš€βœ¨

Multiline Strings - Preserving formatting

Let’s take that Ruby example and give it a JavaScript spin, Node.js style! JavaScript doesn’t have heredocs, but template literals got our back for multiline strings and interpolation, keeping things nice and tidy. 🎨✨

Here’s how we can create a similarly vibrant and formatted message in JavaScript:

// Setting the scene with some variables
const guest = "Alice";
const partyLocation = "Wonderland";
const time = "6 PM";
const activity = "unbirthday party";

// Crafting our complex, multiline message
const message = `
  Hey ${guest}! πŸŽ‰
  
  Guess what? You're invited to a magical party in ${partyLocation}! πŸ„
  When? At ${time}, sharp. Don't be fashionably late!
  
  We're throwing an ${activity}, and it wouldn't be the same without you.
  
  See you in ${partyLocation}!
`;

console.log(message);

Spot the Differences πŸ•΅οΈβ€β™‚οΈ:

  • Template Literals vs. Heredocs: JavaScript leans on template literals (` `), which, like Ruby’s heredocs, allow for multiline strings and interpolation. Simply use ${} to weave in your variables or any JavaScript expression.

  • Preserving Whitespace: Just as in Ruby, the indentation and line breaks you type inside the template literals are preserved in the output. This means your message keeps its intended layout, making those detailed outputs a breeze.

  • Printing to the Console: Instead of Ruby’s puts, JavaScript uses console.log() to share its message with the world.

And there you have itβ€”a fun and formatted way to convey detailed messages in JavaScript, using template literals. It’s all about bringing a touch of creativity and clarity to your code. So go ahead, craft those messages, and let your code speak volumes! πŸš€πŸŒŸ

Code Reuse with Modules - Structured project organization

This program written in Ruby used class inheritance as a code sharing mechanism. In JavaScript (Node.js) world, we’re setting up a cool project where we mimic class inheritance to share functionality, even without the traditional class-based approach. Let’s get creative with modules and functions since we’re exploring alternatives to class inheritance. πŸš€

Project Vibes:

Instead of a class-based structure, we’ll use JavaScript modules to share and extend functionality, keeping our project organized and modular.

node_project/
|-- base/
|   `-- person.js
|-- people/
|   |-- friend.js
|   `-- family_member.js
`-- main.js

Step 1: The Base Module

In the base directory, we create a simple module for our base functionality.

base/person.js:

function sayYourName() {
  return "I'm just a basic Person.";
}

module.exports = sayYourName;

Step 2: Extending Functionality

In the people directory, we create modules that extend our base functionality, adding their own twists.

people/friend.js:

const sayYourName = require('../base/person');

function friendSayYourName() {
  return `${sayYourName()} turned Friend!`;
}

module.exports = friendSayYourName;

people/family_member.js:

const sayYourName = require('../base/person');

function familyMemberSayYourName() {
  return `${sayYourName()} turned Family Member!`;
}

module.exports = familyMemberSayYourName;

Step 3: Dynamic Invocation and Colorful Output

In main.js, we dynamically decide which module’s function to call and add a splash of color to the output using ANSI color codes.

main.js:

const sayYourName = require('./base/person');
const friendSayYourName = require('./people/friend');
const familyMemberSayYourName = require('./people/family_member');

// ANSI color codes
const colors = {
  red: "\x1b[31m%s\x1b[0m",
  green: "\x1b[32m%s\x1b[0m",
  default: "%s"
};

const arg = process.argv[2] || "person";
const color = process.argv[3] || "default";

let output;
switch(arg.toLowerCase()) {
  case "friend":
    output = friendSayYourName();
    break;
  case "familymember":
    output = familyMemberSayYourName();
    break;
  default:
    output = sayYourName();
}

console.log(colors[color], output);

Running the Show 🎬

Navigate to your project directory in the terminal, and let’s roll:

node main.js friend green
node main.js familymember red

Key Differences and Fun Facts:

  • Modules Over Classes: JavaScript’s modular approach with require and module.exports allows us to share functionality across files, akin to Ruby’s require_relative but flavored with JavaScript’s unique taste.

  • Functional Approach: We used functions and modules to replicate a class-like inheritance structure, demonstrating JavaScript’s flexibility.

  • Colorful Console: Both Ruby and Node.js love adding color to the terminal, but they use slightly different ANSI color codes syntax. Node.js wraps the color code around the %s placeholder in console.log.

And just like that, we’ve crafted a dynamic, modular JavaScript project that feels a bit like Ruby but dances to its own rhythm. Happy coding, and enjoy painting your terminal with colorful outputs! 🌈✨

Error Handling - Graceful data transformation inspired by monads

Let’s dive into transforming this Ruby-inspired approach to handling data transformations with monads into JavaScript land, using Node.js. JavaScript, like Ruby, doesn’t natively support monads out of the box, but we can mimic the pattern to gracefully manage potential errors in our data processing. Let’s keep things light, fun, and functional, shall we? 🎈✨

Crafting Our Maybe Monad

First up, we’re going to build our own little Maybe helper, serving up some of that sweet, sweet monad magic to deal with uncertainties in our data.

class Maybe {
  constructor(value) {
    this.value = value;
  }

  static some(value) {
    return new Maybe(value);
  }

  static none() {
    return new Maybe(null);
  }

  map(fn) {
    if (this.value === null) return this;
    try {
      return Maybe.some(fn(this.value));
    } catch {
      return Maybe.none();
    }
  }
}

Getting Down to Business with Data Transformation

Now, let’s put our Maybe to work, stepping through data fetching, age extraction, and giving our imaginary users a virtual birthday.

// Simulating some data fetching
function fetchUserData(id) {
  if (id <= 0) return Maybe.none();
  return Maybe.some({ name: "Alice", age: 30 });
}

// Extracting the age
function extractAge(data) {
  if (data.age === undefined) return Maybe.none();
  return Maybe.some(data.age);
}

// Celebrating the birthday
function incrementAge(age) {
  return Maybe.some(age + 1);
}

// Orchestrating the whole data transformation dance
function processUserData(userId) {
  return fetchUserData(userId)
    .map(extractAge)
    .map(incrementAge);
}

Running the Show

// Example usage
const result = processUserData(1);
result.value !== null
  ? console.log(`Transformed data: ${result.value}`) // Expected: Transformed data: 31
  : console.error("An error occurred during the data transformation process.");

const badResult = processUserData(-1);
badResult.value !== null
  ? console.log(`Transformed data: ${badResult.value}`)
  : console.error("An error occurred during the data transformation process."); // This line will execute

Key Takeaways and Fun Facts:

  • Monads in JavaScript: While JavaScript doesn’t have monads baked into the language, our Maybe class showcases how we can introduce functional programming concepts to manage data flow and errors gracefully.

  • Error Handling: The try/catch in our map method allows us to gracefully handle exceptions, similar to the rescue block in Ruby’s version, keeping our data transformation pipeline clean and predictable.

  • Language Differences: JavaScript’s class and static method syntax offers a straightforward way to encapsulate our monad logic, differing from Ruby’s more open class modification approach.

And there you goβ€”a JavaScript take on using monads for error handling and data transformation, bringing a bit of functional programming flair to Node.js. Whether you’re fetching data, celebrating virtual birthdays, or just exploring new programming paradigms, remember: the journey is all part of the fun! πŸš€πŸŒŸ

Multithreading and Async - Maximizing CPU usage

Alright, let’s transform that old Ruby script into a Node.js fiesta, cranking up the async vibes! πŸŽ‰ Node.js thrives on non-blocking operations, making it a natural fit for parallel data fetching. Instead of multithreading, we’ll lean on Node’s asynchronous nature and promises to chat up jsonplaceholder. Let’s get this asynchronous party started:

First things first, if you haven’t already, you might want to get cozy with axios for fetching our data. It’s like Net::HTTP but for Node.js, and a bit more friendly. Since we’re assuming a fresh project:

npm install axios

Now, onto the script:

const axios = require('axios');

// URLs we'll be hitting 🎯
const urls = [
  'https://jsonplaceholder.typicode.com/posts/1',
  'https://jsonplaceholder.typicode.com/posts/2',
  'https://jsonplaceholder.typicode.com/posts/3'
];

// Function to fetch data from a URL 🌐
async function fetchData(url) {
  try {
    const response = await axios.get(url);
    return response.data;
  } catch (e) {
    console.log(`Oops! Failed to fetch ${url}: ${e.message} 🚨`);
  }
}

// Let's get this async party started πŸŽ‰
async function fetchAllData() {
  const promises = urls.map(url => fetchData(url).then(data => {
    // Adding some emoji flair to the output 🌟
    console.log(`Fetched from ${url}: ${data.title} πŸ“¬`);
  }));

  await Promise.all(promises);
  console.log("All done! Data fetched in parallel, like a boss 😎");
}

// Kick off the async fetching
fetchAllData();

Here’s What’s Different:

  • Async/Await Over Threads: Node.js uses async/await for managing asynchronous operations, a different approach from Ruby’s threads but equally powerful for running tasks in parallel.

  • Promises, Promises: Instead of spinning up threads, we map our URLs to promisesβ€”a set of async operationsβ€”and then wait for all of them to resolve with Promise.all(). It’s like sending out your squad to grab those snacks and waiting for everyone to get back.

  • Fetching with Axios: We’re using axios for a smoother experience fetching data. It handles the nitty-gritty of HTTP requests and responses, making our code cleaner and our lives easier.

  • Error Handling: Just like in Ruby, we catch any snags in our fetching process, but with try/catch blocks thanks to async/await, keeping our error messages helpful and our output emoji-rich.

And there you go, a Node.js script ready to fetch data in parallel with all the async glory. Node.js’s event-driven nature makes it a powerhouse for this kind of task, proving once again that JavaScript’s async patterns are not just powerful; they’re fun to work with, especially when emojis are involved! πŸš€βœ¨

Testing - be an adult, test your code

Shifting gears to JavaScript with Node.js, let’s recreate that Ruby charm by crafting a Greeter function and testing it to ensure it’s as polite and effective. In the Node.js world, we’ll buddy up with Jest, a delightful testing framework that’s all about making testing fun. πŸŽͺ✨

First Things First: Setup Jest

Before we dive in, let’s make sure Jest is ready to party. In your project directory, kick things off with:

npm init -y
npm install --save-dev jest

This gets you a fresh package.json and installs Jest as a dev dependency. Next, update your package.json to include a test script:

"scripts": {
  "test": "jest"
}

Crafting the Greeter

Now, let’s create our Greeter function in a file named greeter.js. Keeping it simple and sweet:

// greeter.js
function greet(name) {
  return `Hello, ${name}!`;
}

module.exports = greet;

Time for Some Jest Magic

Let’s write some tests to make sure our Greeter function is on its best behavior. Save this as greeter.test.js:

// greeter.test.js
const greet = require('./greeter');

test('greets Alice with warmth', () => {
  expect(greet("Alice")).toBe("Hello, Alice!");
});

test('greets Bob with kindness', () => {
  expect(greet("Bob")).toBe("Hello, Bob!");
});

In these tests, we’re ensuring that greet does exactly what it’s supposed to do when introduced to Alice and Bob. Jest’s expect and toBe give us a fluent way to express our expectations, similar to Ruby’s assert_equal but with a JavaScript flair.

Let the Testing Begin

With our tests in place, let’s run them. Fire up your terminal, navigate to your project’s root, and:

npm test

If Greeter is indeed the courteous function we believe it to be, you’ll see Jest happily report success. 🌟 If something’s amiss, Jest will gently point out what needs your attention, so you can tweak Greeter until it’s just right.

Key Differences & Takeaways

  • Testing Frameworks: While Ruby has MiniTest out of the box, Node.js has a plethora of testing frameworks to choose from, with Jest being a popular pick for its simplicity and feature-rich environment.

  • Module System: JavaScript uses require and module.exports to share code between files, a bit different from Ruby’s require and require_relative but serving the same purpose of making functions available where they’re needed.

  • Test Structure: Jest tests are structured a bit differently, using test blocks and matcher functions like toBe to assert conditions, showcasing JavaScript’s flexible approach to expressing test logic.

And just like that, you’ve set up a polite Greeter in Node.js, backed by Jest tests to ensure it never steps out of line. Remember, testing is your ally in the quest for reliable, bug-free code. Here’s to many happy greetings in your coding adventures! πŸš€πŸ’–

Wrap up

And just like that, you’ve surfed the JavaScript waves! πŸ„ You’ve navigated through asynchronous patterns, bonded with callbacks, promises, and async/await, and sprinkled your projects with a dash of emoji fun. Not stopping there, you’ve bravely ventured into the realms of testing with Jest, ensuring your code stands strong and reliable. This journey through JavaScript isn’t just a series of coding exercises; it’s a foundational step that paves the way for adventures in other dynamic languages - bringing Ruby’s elegance, Python’s clarity, TypeScript’s precision, Elixir’s functional charm, Swift’s mobile prowess, and Haskell’s purity into your developer toolkit.

JavaScript, with its event-driven nature and non-blocking I/O, offers a unique perspective on handling operations, especially when it comes to web development. Yet, the core principles you’ve embraced here, from modular code organization, embracing functional programming concepts, to rigorous testing, are universally valuable. They’re your coding compass, guiding you through the vast seas of programming languages. 🧭🌐

As you transition from JavaScript’s event-loop adventures to the typed territories of TypeScript, the enchanting functional paradigms of Elixir, Swift’s domain of mobile applications, and even Haskell’s realm of pure functions, carry forward the lessons learned. Remember, each language is a new dialect in the broader conversation of technology, offering fresh ways to solve problems and bring your ideas to digital life.

So, gear up! πŸš€ You’re now well-equipped with JavaScript’s versatile toolkit, ready to dive into TypeScript for some type safety, explore Python’s straightforward syntax, or even dabble in Ruby for some object-oriented elegance. The road from here is rich with possibilities, and with JavaScript under your belt, you’re ready to explore, innovate, and create across a spectrum of programming landscapes.

Keep that spark of curiosity alight, and let it guide you to new discoveries and achievements in your coding journey. Here’s to the endless opportunities that await you, to the challenges you’ll overcome, and to the incredible projects you’ll build. Happy coding, and may your path be filled with learning, growth, and lots of fun code! πŸŽ‰πŸ’‘

Related Posts

Why you won't find a technical cofounder

Why you won't find a technical cofounder

Oh boy, strap in, ‘cause let me tell you why snagging that mythical tech co-founder online is kinda like rocket surgery πŸš€πŸ’‰.

Read More
The Myth of the Non-Technical Engineering Manager

The Myth of the Non-Technical Engineering Manager

Does engineering manager need to be “technical”?

Read More
Python - learn 80% of a new language in a week

Python - learn 80% of a new language in a week

Having spent my time with TS, Swift and Haskell I’m beyond excited to go back to an interpreted language.

Read More