
JavaScript - learn 80% of a new language in a week
- Shem
- March 3, 2020
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:
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!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.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’.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.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.Flip It π:
.reverse()simply reverses the order. Imagine walking backward just for the fun of itβthat’s what we’re doing here.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.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:
- Crafting XML: We kick off with a cozy XML tale about Joey and Rachel. π
- XML to JavaScript Object: We leverage
xml2jsto turn our XML into a friendly JavaScript object. It’s like decoding a secret message. π - 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. β
- JavaScript Object to JSON: A simple
JSON.stringifyturns our object into stylish, web-ready JSON. π« - 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
csvandnet/http), we leaned oncsv-parserandaxiosfor 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
jsonlibrary. Node.js treats JSON like the native it is.Writing CSVs: We used
json2csvfor 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:
Default Case (chills with “Hello, World!”):
node helloWorld.jsUppercase (boldly shouts “HELLO, WORLD!”):
node helloWorld.js upperLowercase (whispers “hello, world!”):
node helloWorld.js lower
Spot the Differences π§
- Modules Galore: In Node.js, we use
module.exportsto share our functions across files, a bit different from Ruby’srequire_relativebut equally slick. - Command-Line Chats: Node.js taps into
process.argvfor command-line arguments, starting the count at2because 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:
- Whipping up a CSV in memory: Imagine you’re in the kitchen, but instead of breadcrumbs, you’re sprinkling data.
- Uploading to S3: Picture posting that perfect selfie to the cloud.
- Album swap: Deciding it looks better in a different gallery.
- Storage upgrade: Like moving that selfie from a dusty album to a premium cloud spot.
- Retrieval: Sometimes, you just gotta have that photo back on your device.
- 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
requireto import packages. AWS and CSV operations are handled byaws-sdkandcsv-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:
fsfor file operations andzlibfor 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 withrequire. Sincechild_processis built-in, no npm install dance required. - Callbacks Over Synchronous Calls: Node.js loves non-blocking operations.
execuses a callback pattern, where we handle the output or error asynchronously, unlike Ruby’s more straightforward, synchronouscapture3. - 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, andps auxare 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 usesconsole.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
requireandmodule.exportsallows us to share functionality across files, akin to Ruby’srequire_relativebut 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
%splaceholder inconsole.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
Maybeclass showcases how we can introduce functional programming concepts to manage data flow and errors gracefully.Error Handling: The
try/catchin ourmapmethod 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
axiosfor 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/catchblocks 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
requireandmodule.exportsto share code between files, a bit different from Ruby’srequireandrequire_relativebut serving the same purpose of making functions available where they’re needed.Test Structure: Jest tests are structured a bit differently, using
testblocks and matcher functions liketoBeto 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! ππ‘