
TypeScript - learn 80% of a new language in a week
- Shem
- April 1, 2020
We’re continuing our series of articles where we take 12 relatively simple coding assignments to learn 80% of a new language in a week.
π Ready to add some superpowers to your JavaScript skills? Welcome to our TypeScript tutorial, where we’re about to embark on an adventure that bridges the dynamic world of JavaScript (JS) with the robust, type-enhanced universe of TypeScript (TS). If you’ve been tinkering with JavaScript and love what you can do with it but sometimes find yourself wishing for a bit more “oomph” in terms of predictability and scale, then TypeScript is your new best friend. π
In this journey, we’re not just learning a new language; we’re discovering a new way to write JavaScript. TypeScript takes all the JavaScript you know and love and adds a layer of types on top, making your code more readable, more predictable, and easier to debug. It’s like having a wise mentor looking over your shoulder as you code, offering guidance and saving you from potential pitfalls down the road. π§ββοΈβ¨
We’ll start by comparing some classic JavaScript examples to their TypeScript counterparts. It’s like looking at before-and-after photos that show off the magic of a little structure and organization. By seeing these comparisons, you’ll get a feel for how TypeScript enhances your workflow, improves code quality, and makes collaboration with others a breeze. Plus, we’ll sprinkle in some fun along the way because, let’s face it, coding should be enjoyable, right?
So, grab your favorite cup of coffee β, get comfy, and let’s dive into the world of TypeScript together. By the end of this tutorial, you’ll not only have a new set of coding skills but also a deeper appreciation for the art of coding itself. Let’s turn those JavaScript ideas into TypeScript realities!
Compiliation - go slow to go fast
We’re getting out of dynamic language garden, so now between writing and running our code we add extra step - compilation.
To get TypeScript rolling on your Mac, you’ll first need to have Node.js installed since TypeScript relies on Node’s package manager, npm, for installation. Assuming Node.js is ready to party on your system, here’s how you install TypeScript globally, so you can use it in any project:
npm install -g typescript
This command pulls TypeScript from the npm registry and installs it globally on your machine, making the TypeScript compiler tsc available in your terminal.
Now, let’s say you’ve got a TypeScript file named app.ts that you’re itching to compile into JavaScript and then run with Node.js. Here’s how you’d do that in two steps:
- Compile TypeScript to JavaScript:
tsc app.ts
This command uses the TypeScript compiler to transform app.ts into app.js. If app.ts is in another directory, just include the path to the file.
- Run the compiled JavaScript file with Node.js:
node app.js
Depending on your setup you might encounter some errors, so just google them. Here’s one I found
app.ts:1:30 - error TS2550: Property 'from' does not exist on type 'ArrayConstructor'. Do you need to change your target library? Try changing the 'lib' compiler option to 'es2015' or later.
1 const result: string = Array.from({ length: 6 }, (_, i) => i + 97) // Still rounding up those numbers
It just means you need to use proper settings for your compiler. Just create a “TS-Tutorial” directory and create a file named tsconfig.json with those contents:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true
}
}
Now your tcs should work just fine like so tcs app.ts and node dist/app.js
Working with Lists - Wrangling data efficiently
Jumping from JavaScript (JS) to TypeScript (TS), let’s reimagine this script in a TypeScript setting, adding a touch of type safety and sophistication to our code. TypeScript, being a superset of JavaScript, enhances our coding experience with types and interfaces, giving our dynamic JS a bit of structure. Here’s how we can sprinkle some TypeScript magic on this JS snippet:
The TypeScript Twist:
const result: string = Array.from({ length: 6 }, (_, i) => i + 97) // Still rounding up those numbers
.filter((num: number): boolean => num % 2 !== 0) // TypeScript knows these are numbers now
.map((num: number): string => String.fromCharCode(num)) // And it knows we're dealing with strings here
.sort((a: string, b: string): number => /[aeiouy]/.test(a) ? -1 : 1) // Explicitly sorting strings
.map((chr: string): string => chr.toUpperCase()) // Uppercasing those characters
.slice(0, 2) // Selecting the top two characters
.join(''); // Merging them into our final, glorious string
console.log(result); // "AE", TypeScript style!
Breaking Down the Differences:
Type Annotations: With TypeScript, we’re not just passing around variables; we’re ensuring they come with a promise of what type they hold. This adds a layer of predictability and safety to our code, catching type errors before they run.
Function Parameter Types: We’re being explicit about what type of parameters our functions expect and what type they return. TypeScript is all about making sure we don’t accidentally mix up our types, leading to fewer runtime surprises.
Why TypeScript?: TypeScript adds a layer of clarity to our code, not just for the machine, but for fellow humans (or future you) reading the code. It’s like adding labels to your food containers; you’ll thank yourself later.
Compilation Step: Unlike JavaScript, which can be directly run by the browser or Node.js, TypeScript needs a compilation step to turn into JavaScript. This step checks our types and then translates our TS into runnable JS.
Running TypeScript: To run TypeScript, you’ll typically use the
tsccommand (TypeScript compiler) to compile your.tsfiles into.js, and then run the resulting JavaScript file with Node.js or in the browser.
Why Bother with TypeScript?
You might be wondering, “If TypeScript just turns into JavaScript in the end, why bother?” Well, TypeScript’s type system helps catch errors early in the development process, making your code more robust and maintainable. Plus, many IDEs offer better autocompletion and inline documentation for TypeScript, speeding up your development process.
TypeScript’s blend of flexibility (from JavaScript) and type safety (inspired by more strictly typed languages) makes it a powerful tool for developing large-scale applications, enhancing code quality and developer productivity. So, as you wave goodbye to JavaScript’s free-spirited nature and embrace TypeScript’s structured elegance, you’re equipping yourself with a toolset designed for building more reliable and scalable applications. Happy coding with TypeScript! πβ¨
JSON/XML/YAML Mapping - Converting between formats and objects
Let’s put on our TypeScript hats π and refactor this fun Node.js journey from XML to JSON into TypeScript, adding type safety and making it even more awesome. TypeScript, like a meticulous librarian, requires us to be clear about the types of books (or in this case, variables) we’re dealing with. So, let’s type our way through the conversion and see where JavaScript might slip without TypeScript’s watchful eyes.
First, ensure you’ve got the xml2js package and its TypeScript type definitions installed in your Node.js project:
npm install xml2js
npm install --save-dev @types/xml2js
Now, let’s translate the script into TypeScript, focusing on where we’d introduce types and interfaces for that sweet, sweet type safety:
import * as xml2js from 'xml2js';
// Step 0: Define TypeScript interfaces for our friend objects
interface Friend {
name: string[];
age: string[];
favoriteCoffee?: string[];
}
interface FriendsList {
friend: Friend[];
}
// The xml2js parser setup
const parser = new xml2js.Parser();
// Step 1: Craft some XML
const xmlData: string = `
<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: Error, result: any) => {
if (err) {
throw err; // In TypeScript, we're clear about `err` being an Error πΆοΈ
}
// Let's assure TypeScript of what we're dealing with using interfaces
const friends: FriendsList = result.friends;
// Mapping XML to our cozy TypeScript object
const friendsObj = friends.friend.map((friend: Friend) => ({
name: friend.name[0],
age: parseInt(friend.age[0], 10),
favoriteCoffee: friend.favoriteCoffee ? friend.favoriteCoffee[0] : "Cappuccino", // Defaulting to Cappuccino β
}));
// Step 3: Make some edits
friendsObj.forEach(friend => {
friend.age += 1; // TypeScript keeps us honest about `age` being a number π
});
// Step 4: Convert our updated object to JSON
const jsonOutput: string = JSON.stringify(friendsObj, null, 2);
// Step 5: Show off our masterpiece
console.log(jsonOutput); // Now with TypeScript's blessing π©β¨
});
Key TypeScript Touches:
- Interfaces for Days: By defining
FriendandFriendsList, TypeScript helps us ensure that our objects have the right shape and types. JavaScript would just shrug and let us figure it out at runtime. - Explicit Error Handling: TypeScript makes us explicit about
errbeing of typeError, providing better clues during debugging. - Type Assertions: When we deal with external data (like parsed XML), TypeScript can’t magically know what’s in it. By declaring the shape of our data with interfaces, we make our expectations clear, reducing the risk of runtime surprises.
In JavaScript, we’d likely skate by without worrying about these types until a runtime error smacks us in the face. TypeScript, with its compile-time checks, gives us a heads-up, ensuring our data transformations are on the up and up before we even run our code.
So, as you embark on this TypeScript-ified journey of transforming XML to JSON, enjoy the type safety, clearer code, and fewer runtime errors. Remember, TypeScript’s not just about being strict; it’s about making your code more predictable, maintainable, and just plain better. Happy coding in TypeScript land! πβ¨
Dictionary Reversal - Flipping keys and values
Translating this JavaScript concept into TypeScript involves adding type annotations to our function for more clarity and type safety. TypeScript shines by letting us define the shape of our inputs and outputs, helping catch type-related errors during compilation rather than at runtime. Let’s dive into how we can TypeScript-ify this dictionary reversal function, focusing on where JavaScript might let errors slip through unnoticed. π΅οΈββοΈβ¨
function swapKeysAndValues(originalObj: { [key: string]: any }): { [key: string]: string | string[] } {
// Initialize a new object to store our swapped pairs with explicit typing.
const swappedObj: { [key: string]: string | string[] } = {};
// Loop through each key-value pair in the original object.
Object.entries(originalObj).forEach(([key, value]) => {
const valueAsString = String(value); // Ensure our value is a string to use as a key.
// Check if the value (now a key) already exists in our swapped object.
if (swappedObj.hasOwnProperty(valueAsString)) {
// Ensure it's an array before pushing the new key.
swappedObj[valueAsString] = Array.isArray(swappedObj[valueAsString])
? swappedObj[valueAsString]
: [swappedObj[valueAsString]];
(swappedObj[valueAsString] as string[]).push(key);
} else {
// For unique values, simply assign them as keys with the original key as their value.
swappedObj[valueAsString] = key;
}
});
return swappedObj;
}
// Example usage with a more explicit type for the input object.
const exampleObj: { [key: string]: number } = { 'a': 1, 'b': 2, 'c': 1 };
console.log("Original:", exampleObj);
const swapped = swapKeysAndValues(exampleObj);
console.log("Swapped:", swapped);
TypeScript Touches and Differences:
Explicit Types: We start by defining the shape of our input and output objects using TypeScript’s index signature syntax:
{ [key: string]: any }for the input and{ [key: string]: string | string[] }for the output. This ensures our function expects and returns objects with string keys, where the values can either be a string or an array of strings.Type Assertions: When we encounter a previously seen value, we want to add the current key to an array. TypeScript requires us to assure it that
swappedObj[valueAsString]is indeed an array before we can push to it, hence the(swappedObj[valueAsString] as string[]).push(key)part.Handling Non-String Values: Notice how we convert
valueto a string withString(value). This is crucial because, in JavaScript, using non-string values as object keys can lead to unexpected behavior due to automatic type coercion. TypeScript helps us catch these subtleties by making us more explicit about types.TypeScript’s Type Safety: The added type annotations help TypeScript protect us from potential runtime errors, such as accidentally assigning a non-string value as an object key or trying to push to a non-array value.
In JavaScript, especially when dealing with dynamic or loosely structured data, it’s easy to overlook type-related errors until they cause problems at runtime. TypeScript’s compile-time type checking offers a safety net, encouraging you to think through your data structures and function contracts more thoroughly.
By leveraging TypeScript, we gain clarity and robustness in our code, making our dictionary reversal function not just functional but also more predictable and easier to maintain. Happy coding with TypeScript, where the types are your friends, guiding you towards safer and more reliable code! ππ
Handling IO - Getting dirty
Let’s TypeScript-ify this Node.js script to bring some type safety to our CSV parsing, API calling, random waiting, and progress tracking shenanigans! With TypeScript, we’ll explicitly state what we expect, leading to clearer, more maintainable code. Plus, TypeScript will scold us at compile time instead of runtime if we mess up, which is pretty neat. π€β¨
First, ensure csv-parser, axios, and json2csv are joined by their TypeScript type definition pals in your project:
npm install csv-parser axios json2csv
npm install --save-dev @types/csv-parser @types/axios
Now, let’s dive into our TypeScript script:
import * as csv from 'csv-parser';
import * as fs from 'fs';
import axios from 'axios';
import { parse } from 'json2csv';
interface Person {
Name: string;
Age: string;
Email: string;
}
interface ApiData {
data?: object;
error?: string;
}
const inputFilePath = './input.csv';
const outputFilePath = './output.csv';
function readCsvData(filePath: string): Promise<Person[]> {
return new Promise((resolve, reject) => {
const results: Person[] = [];
fs.createReadStream(filePath)
.pipe(csv())
.on('data', (data: Person) => results.push(data))
.on('end', () => resolve(results))
.on('error', reject);
});
}
function filterData(rows: Person[], condition: (row: Person) => boolean): Person[] {
return rows.filter(condition);
}
async function fetchUserData(id: number): Promise<ApiData> {
try {
const response = await axios.get(`https://jsonplaceholder.typicode.com/users/${id}`);
await new Promise(resolve => setTimeout(resolve, Math.random() * 1000));
return { data: response.data };
} catch (error) {
return { error: `Failed to fetch data for ID ${id}` };
}
}
async function writeToCsv(filename: string, data: Person[]): Promise<void> {
const csv = parse(data);
fs.writeFileSync(filename, csv);
}
async function processCsv(inputFile: string, outputFile: string): Promise<void> {
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}.`);
}
processCsv(inputFilePath, outputFilePath);
Spot the TypeScript Enhancements:
Interfaces: By defining
PersonandApiData, TypeScript helps us ensure that our CSV data and API responses fit the bill. JavaScript would let us play fast and loose with types, but TypeScript insists on clarity.Function Annotations: Our functions now clearly state what they take in and spit out, thanks to TypeScript. This makes our code easier to follow and debug.
Error Handling: TypeScript’s type checking extends to our error handling, making sure we’re prepared for whatever our API calls throw at us, literally and figuratively.
Async/Await: Just like in JavaScript, but with the added confidence that comes from TypeScript’s type checks, ensuring our asynchronous code behaves as expected.
Where TypeScript really stands out is in preventing the “uh-oh” moments before they happen, thanks to its compile-time type checking. So, while the script’s core logic remains the same as our JavaScript version, TypeScript adds a layer of robustness and predictability to our Node.js adventures. Now, you’re not just handling IO; you’re doing it with style and safety. ππ
Basic Project Setup - Hello World with options
Let’s upgrade our Node.js journey to TypeScript town, where types reign supreme, and code safety is the name of the game. TypeScript adds a layer of type checking to our JavaScript adventures, turning potential runtime errors into compile-time alerts. π¨π
For our “Hello World” script with a twist, we’ll need to adjust how we handle module imports/exports compared to JavaScript. Plus, we’ll dive into setting up a basic tsconfig.json to get TypeScript rolling smoothly with Node.js.
Step 0: TypeScript Setup
Before we translate our code, ensure TypeScript is installed in your project. If not, bring it on board:
npm install typescript --save-dev
Next, initialize a TypeScript config file:
npx tsc --init
This command creates a tsconfig.json file with default settings. For our simple project, we’ll tweak it a bit to align with Node.js module resolution and our target runtime environment:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true
},
"include": ["src/**/*"]
}
- “target”: “es6”: Compiles TypeScript to ES6 JavaScript, compatible with modern Node.js versions.
- “module”: “commonjs”: Uses CommonJS module resolution, the style used by Node.js.
- “outDir”: “./dist”: Directs compiled JavaScript files into the
distfolder. - “include”: [“src//*”]**: Tells TypeScript to include files in the
srcdirectory.
Step 1: upperCase.ts
TypeScript loves explicit exports:
// src/upperCase.ts
export const upperCase = (): void => {
console.log("HELLO, WORLD!");
};
Step 2: lowerCase.ts
And it’s the same deal for our lowercase function:
// src/lowerCase.ts
export const lowerCase = (): void => {
console.log("hello, world!");
};
Step 3: helloWorld.ts
Now, the main event, where we decide how loudly to greet the world:
// src/helloWorld.ts
import { upperCase } from './upperCase';
import { lowerCase } from './lowerCase';
const arg = process.argv[2];
if (arg === "upper") {
upperCase();
} else if (arg === "lower") {
lowerCase();
} else {
console.log("Hello, World!");
}
Running Your TypeScript
Compile your TypeScript to JavaScript with the TypeScript compiler:
npx tsc
This command turns your .ts files into .js files in the dist folder. To run your compiled helloWorld.js script:
node dist/helloWorld.js
Key TypeScript Takeaways π§
- Type Safety: By specifying our functions return
void, TypeScript checks that we don’t accidentally return a value. - Module Syntax: TypeScript prefers ES6 module syntax (
import/export) over CommonJS, though it compiles down torequireandmodule.exportsfor Node.js compatibility due to ourtsconfig.jsonsettings. - Project Structure: We’ve introduced a
srcanddistdirectory structure, keeping our source TypeScript separate from the compiled JavaScript, a common pattern in TypeScript projects.
And voila! You’ve just given your Node.js script a TypeScript makeover, introducing type safety and modular structure to your project. TypeScript’s compile-time checks add a robust layer of error prevention, keeping those sneaky runtime bugs at bay. So dive in, enjoy the TypeScript-enhanced development experience, and keep those console greetings coming! ππ»
AWS S3 Interaction - File operations in the cloud
Time to give this Node.js script a TypeScript makeover, bringing in the power of types for a more secure and error-proof interaction with AWS S3. With TypeScript, we’re not just writing JavaScript; we’re ensuring our objects, functions, and interactions are predictably structured. π Let’s get started by ensuring our workspace is TypeScript-ready:
First, ensure the aws-sdk, csv-writer, and their types, along with TypeScript itself, are installed:
npm install aws-sdk csv-writer
npm install --save-dev typescript @types/node @types/aws-sdk
Note: zlib and fs come with Node.js, and their types are included in @types/node.
Let’s TypeScript-ify the Script π
import AWS from 'aws-sdk';
import { createWriteStream, createReadStream } from 'fs';
import { createGzip } from 'zlib';
import { createObjectCsvStringifier } from 'csv-writer';
AWS.config.update({ region: 'your-own-special-place' });
const s3 = new AWS.S3();
const sourceBucket: string = 'your-original-album';
const destinationBucket: string = 'your-new-fancy-album';
const fileName: string = 'amazing_data.csv';
const objectKey: string = `secret-folder/${fileName}`;
interface DataRecord {
name: string;
age: string;
}
const csvWriter = createObjectCsvStringifier({
header: [
{ id: 'name', title: 'Name' },
{ id: 'age', title: 'Age' }
]
});
const data: DataRecord[] = [
{ name: "Alice", age: "30" },
{ name: "Bob", age: "25" }
];
const csvContent: string = csvWriter.stringifyRecords(data);
// Uploading to S3
const uploadParams: AWS.S3.PutObjectRequest = {
Bucket: sourceBucket,
Key: objectKey,
Body: csvContent
};
s3.putObject(uploadParams).promise()
.then(() => console.log("Uploaded"))
.catch(console.error);
// Moving the File
const copyParams: AWS.S3.CopyObjectRequest = {
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);
// Bringing it Back and Squishing
const downloadParams: AWS.S3.GetObjectRequest = {
Bucket: destinationBucket,
Key: objectKey
};
const fileStream = createWriteStream(fileName);
s3.getObject(downloadParams).createReadStream()
.pipe(fileStream)
.on('finish', () => {
const readStream = createReadStream(fileName);
const writeStream = createWriteStream(`${fileName}.gz`);
const gzip = createGzip();
readStream.pipe(gzip).pipe(writeStream).on('finish', () => {
console.log("All done! Your file's been through quite the adventure.");
});
});
TypeScript vs. JavaScript: Key Takeaways π§
- Type Annotations: Everywhere! From simple strings to complex AWS request objects, TypeScript requires explicit types, reducing the chance of runtime surprises.
- Interface for Data:
DataRecordinterface helps TypeScript understand the shape of data we’re working with, catching any mismatches early. - AWS SDK and CSV Writer: Using TypeScript definitions for
aws-sdkandcsv-writerenhances code reliability by providing autocomplete suggestions and compile-time type checking. - Node.js Modules: Import statements are cleaner and more consistent with ES6 syntax, though compiled down to CommonJS under the hood for Node.js compatibility.
While the core logic remains largely the same, TypeScript’s explicit type system introduces a robust layer of predictability and safety to our AWS S3 interactions and file operations. Enjoy the enhanced developer experience and peace of mind that TypeScript brings to your Node.js projects! ππΌ
OS Communication - Integrating with the host system
Shifting our Node.js script to TypeScript isn’t just about swapping require for import; it’s about embracing TypeScript’s safety nets, like type declarations and interfaces, to catch potential slips before they happen. Let’s transform our command-line ninja script into TypeScript, focusing on where types make a difference
TypeScript-ified Ninja Script
import { exec } from 'child_process';
// Let's define a type for our executeCommand function's callback parameters
interface ExecCallback {
(error: Error | null, stdout: string, stderr: string): void;
}
// Now, with our type in place, we define the function
const executeCommand = (cmd: string): void => {
console.log(`Executing: ${cmd}`);
exec(cmd, (error: Error | null, stdout: string, stderr: string) => {
if (error) {
console.error(`Error:\n${stderr}`);
} else {
console.log(`Output:\n${stdout}`);
}
} as ExecCallback);
};
// Example: List directory contents
executeCommand('ls -l');
// Expected output (varies based on your current directory's contents):
// 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
// Example: Check disk usage
executeCommand('df -h');
// Expected output (varies based on your system):
// Filesystem Size Used Avail Use% Mounted on
// /dev/sda1 50G 15G 33G 30% /
// Example: Find CPU-hungry processes
executeCommand('ps aux --sort=-%cpu | head -5');
// Expected output (varies based on current processes):
// 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
// Example: Check current network connections
executeCommand('netstat -tuln');
// Expected output (varies based on your network connections):
// Active Internet connections (only servers)
// Proto Recv-Q Send-Q Local Address Foreign Address State
// tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
// Example: Display the current date and time
executeCommand('date');
// Expected output:
// Tue Jul 7 12:34:56 PDT 2020
Spot the TypeScript Touches π΅οΈββοΈ
Explicit Imports: TypeScript uses ES module syntax (
import/export) for a more modern and clearer module management system compared to Node.js’srequire.Typed Callbacks: By defining an
ExecCallbackinterface, we make explicit the types of parameters our callback expects. This adds a layer of type safety, ensuring we handle the function’s arguments correctly within the callback.Command Execution with Types: With
execimported fromchild_process, TypeScript will ensure we’re using it as intended by the Node.js API, helping avoid misuse or typos that could lead to runtime errors.Async Nature Highlighted: TypeScript’s type system doesn’t change the asynchronous nature of
exec, but by forcing us to acknowledge the types, it makes us more conscious of handling asynchronous operations correctly, such as dealing with errors and output.
Multiline Strings - Preserving formatting
Blasting off from JavaScript into the TypeScript galaxy π, we’re about to give our multiline string message a bit of a type-safety boost. While TypeScript orbits around the same sun as JavaScript, it brings its own cosmic dust of types and interfaces to the mix. Let’s see how our multiline string message translates when we sprinkle it with some TypeScript magic. πβ¨
Setting the Scene with Types
As always in TypeScript, we start by declaring our variables with types. It’s like telling the universe exactly what kind of cosmic matter we’re working with:
// Introducing our cast with explicit types
const guest: string = "Alice";
const partyLocation: string = "Wonderland";
const time: string = "6 PM";
const activity: string = "unbirthday party";
Crafting Our Complex, Multiline Message
Using template literals, just as in JavaScript, but with the added confidence that our variables are of the expected types:
// Constructing our message with the precision of a laser beam
const message: string = `
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 in the TypeScript Universe π΅οΈββοΈπ :
Template Literals Remain the Same: Just like in JavaScript, TypeScript uses template literals for multiline strings and interpolation. It’s a universal language across the cosmos of ECMAScript-based languages.
Console.log Stays the Course: Printing to the console doesn’t change. It’s like the universal translator of coding languagesβno matter where you’re from,
console.log()speaks your language.
Code Reuse with Modules - Structured project organization
Let’s launch this JavaScript modular adventure into the TypeScript universe, where we navigate the cosmos with a bit more structure in our ship’s blueprint. πβ¨ In TypeScript land, we’re still about modules and functions, but we also get to declare what type of cosmic particles we’re dealing with. No more guessing games!
Starship Layout:
Our project structure remains the same, but with a TypeScript twist. We’re still avoiding the gravitational pull of traditional class-based inheritance, instead opting for the modular federation of functions. Our spaceship’s compartments look something like this:
ts_project/
|-- base/
| `-- person.ts
|-- people/
| |-- friend.ts
| `-- family_member.ts
`-- main.ts
Module 1: The Base Functionality Core
Our base module is where we define the essence of our entity, the Person.
base/person.ts:
// No longer just a function, but a declaration of intent
export const sayYourName = (): string => {
return "I'm just a basic Person.";
};
Module 2: Galactic Extensions
Our people modules extend the base functionality, adding their own universal spin.
people/friend.ts:
import { sayYourName } from '../base/person';
export const friendSayYourName = (): string => {
return `${sayYourName()} turned Friend!`;
};
people/family_member.ts:
import { sayYourName } from '../base/person';
export const familyMemberSayYourName = (): string => {
return `${sayYourName()} turned Family Member!`;
};
Command Center: Dynamic Invocation and Stellar Output
Our main.ts is the cockpit from which we control which part of our universe to interact with, adding a dash of cosmic color.
main.ts:
import { sayYourName } from './base/person';
import { friendSayYourName } from './people/friend';
import { familyMemberSayYourName } from './people/family_member';
// Defining our color palette with a type
type Color = "red" | "green" | "default";
const colors: Record<Color, string> = {
red: "\x1b[31m%s\x1b[0m",
green: "\x1b[32m%s\x1b[0m",
default: "%s"
};
const arg: string = process.argv[2] || "person";
const color: Color = process.argv[3] as Color || "default";
let output: string;
switch(arg.toLowerCase()) {
case "friend":
output = friendSayYourName();
break;
case "familymember":
output = familyMemberSayYourName();
break;
default:
output = sayYourName();
}
console.log(colors[color], output);
Orbiting Through the Terminal π
Your command to launch the starship remains similar, but now it’s flying through the TypeScript nebula:
node main.ts friend green
node main.ts familymember red
Celestial Observations:
Type Declarations: By declaring our functions and variables with types, we’ve set up a navigational system that prevents us from drifting into black holes (runtime errors).
Import/Export Syntax: TypeScript uses ES6 module syntax out of the box, giving us a clearer and more modern way to share our cosmic modules across the galaxy.
Cosmic Color Codes: Just like in our JavaScript universe, adding color to the terminal makes our journey through the code cosmos more vibrant. TypeScript doesn’t change how we do this, but it makes sure our colors match the expected types.
As we float back down from this TypeScript space expedition, remember that while TypeScript adds structure and safety to our journey, the essence of our missionβcrafting modular, reusable codeβremains unchanged. It’s about exploring new ways to organize and share functionality, whether through classes, modules, or functions, each with its own flavor in the vast coding cosmos. Happy explorations, space coder! π π¨
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! ππ
Embarking on a quest to translate the JavaScript voyage of handling data transformations with monads into the TypeScript realm, we’re about to supercharge our journey with type declarations and the power of interfaces. π€π Let’s engineer our TypeScript monad, not just as a pattern for managing potential errors, but as a blueprint for our data-processing spacecraft.
Assembling Our Maybe Monad Blueprint
In the TypeScript universe, we refine our Maybe monad with types, ensuring that every piece of data and function aboard our spacecraft knows its role and purpose:
class Maybe<T> {
constructor(private value: T | null) {}
static some<T>(value: T): Maybe<T> {
return new Maybe(value);
}
static none<T>(): Maybe<T> {
return new Maybe<T>(null);
}
map<U>(fn: (value: T) => U): Maybe<U> {
if (this.value === null) return Maybe.none<U>();
try {
const result = fn(this.value);
return Maybe.some(result);
} catch {
return Maybe.none<U>();
}
}
}
Deploying Our Data Transformation Modules
With our Maybe monad constructed, it’s time to navigate through our data transformation process, equipped with the precision of TypeScript’s type system:
interface UserData {
name: string;
age: number;
}
// Simulating a data fetching module
function fetchUserData(id: number): Maybe<UserData> {
if (id <= 0) return Maybe.none<UserData>();
return Maybe.some({ name: "Alice", age: 30 });
}
// Module for extracting age
function extractAge(data: UserData): Maybe<number> {
if (data.age === undefined) return Maybe.none<number>();
return Maybe.some(data.age);
}
// Module celebrating a user's birthday
function incrementAge(age: number): Maybe<number> {
return Maybe.some(age + 1);
}
// Command module orchestrating the data transformation
function processUserData(userId: number): Maybe<number> {
return fetchUserData(userId)
.map(extractAge)
.map(incrementAge);
}
Navigating Through Our Data Cosmos
// Initiating our data transformation journey
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
Cosmic Observations and Robotic Insights:
Type Safety in the Void: Our TypeScript
Maybemonad introduces type safety, guiding our data through the transformation process with the certainty of a star map. It’s as if our spacecraft’s computer is constantly checking for asteroid belts (errors) and calculating safe passage.Functional Programming Thrusters: TypeScript enhances JavaScript’s functional programming capabilities with types, making our
mapmethod not just a tool for data transformation but a safeguard against type mismatches in the vast expanse of code.Interstellar Error Handling: The
try/catchwithin ourmapmethod acts like an emergency protocol, ensuring that even when encountering unexpected anomalies (errors), our data processing can continue gracefully or halt safely.
This TypeScript journey through data transformation with monads is more than just a translation from JavaScript; it’s an upgrade to our spacecraft’s systems, providing stronger type checks and clearer communication between modules. π€β¨ Whether you’re navigating through the complexities of data fetching or celebrating the incremental victories of data processing, TypeScript’s type system is your co-pilot, ensuring a smoother and safer journey across the coding cosmos.
Multithreading and Async - Maximizing CPU usage
Diving into the TypeScript lab, let’s upgrade our Node.js async fiesta with TypeScript’s precision instruments. π§ͺπ¬ Remember, in the Node.js ecosystem, leveraging all your CPU cores means embracing asynchronous operations and promises, rather than relying on traditional multithreading techniques. TypeScript, with its type-checking prowess, ensures our scientific expedition into parallel data fetching is both efficient and error-free. Let’s concoct our experiment:
Gathering Our Equipment
Before we start, ensure axios is part of your lab kit for fetching data from the vast data cosmos. TypeScript, acting as our meticulous lab assistant, helps us keep our types and promises in check:
npm install axios @types/axios
The Experiment Begins
import axios from 'axios';
// Identifying our data galaxies π
const urls: string[] = [
'https://jsonplaceholder.typicode.com/posts/1',
'https://jsonplaceholder.typicode.com/posts/2',
'https://jsonplaceholder.typicode.com/posts/3'
];
// A meticulous function to extract data from the universe π
async function fetchData(url: string): Promise<void> {
try {
const response = await axios.get(url);
console.log(`Fetched from ${url}: ${response.data.title} π¬`);
} catch (e) {
console.error(`Oops! Failed to fetch ${url}: ${(e as Error).message} π¨`);
}
}
// The grand async symposium π
async function fetchAllData(): Promise<void> {
const promises = urls.map(url => fetchData(url));
await Promise.all(promises);
console.log("All done! Data fetched in parallel, like a boss π");
}
// Initiating the async data retrieval
fetchAllData();
Observations from the Lab:
Precision Typing: By declaring
urlsas astring[]andfetchDatawith aPromise<void>return type, TypeScript ensures our data types are correctly aligned, like calibrating our lab instruments before the experiment.Async/Await Mechanics: TypeScript adopts the same async/await mechanics from JavaScript, but with the added clarity of types. It’s akin to using a well-calibrated telescope to observe the stars, knowing exactly what you’re looking at.
Promise Aggregation: Mapping our
urlsto a constellation of promises withPromise.all()remains unchanged, yet TypeScript’s type system watches over us like a vigilant AI, ensuring every promise adheres to the expected data structure.Error Handling Precision: The
try/catchmechanism is enhanced by TypeScript allowing us to assert the error as anErrorobject and access itsmessageproperty with confidence, much like a scientist confidently presenting their findings, knowing their data is accurate.
Conclusion from the Lab:
Our TypeScript-based async data fetching operation demonstrates the power of Node.js’s event-driven architecture, amplified by TypeScript’s type safety. Like a well-organized scientific endeavor, every variable, function, and asynchronous operation is meticulously planned and executed, ensuring maximum efficiency and reliability in our data fetching expedition. π§¬π«
While TypeScript and JavaScript share the same async foundations, TypeScript’s type annotations ensure that our expedition into the async cosmos is not only successful but also error-free, letting us fully utilize all the CPU cores we’ve paid for. It’s a testament to the idea that in the modern development landscape, being equipped with the right tools and knowledge makes all the difference. Happy coding, fellow data scientists! ππ
Testing - be an adult, test your code
Let’s elevate our JavaScript Greeter function to the TypeScript domain, employing TypeScript’s type-checking capabilities to ensure our function not only greets politely but also adheres to strict type protocols. In this realm, our code becomes a well-defined experiment, where each variable and function has a clear role, akin to scientists meticulously planning their research. π§¬π
Setting Up the Lab
Our project setup remains similar, but we’re introducing TypeScript to our toolkit. Jest remains our testing framework, now complemented by ts-jest to understand TypeScript’s syntax during our experiments.
Synthesizing the Greeter
Our Greeter function in greeter.ts now explicitly states its intention to receive a string and return a greeting, ensuring no type anomalies occur:
// greeter.ts
export function greet(name: string): string {
return `Hello, ${name}!`;
}
Conducting Jest Experiments
Our testing file, greeter.test.ts, now imports our Greeter function with TypeScript’s import syntax, ready to test its interactions with the variables of the universe:
// greeter.test.ts
import { greet } from './greeter';
test('greets Alice with scientific precision', () => {
expect(greet("Alice")).toBe("Hello, Alice!");
});
test('greets Bob with methodological warmth', () => {
expect(greet("Bob")).toBe("Hello, Bob!");
});
In these experiments, we observe how greet reacts when introduced to Alice and Bob, using Jest’s matcher functions to assert that the outcome is exactly as predicted by our hypothesis.
Initiating the Test Sequence
Running our tests in TypeScript doesn’t require any additional incantations. Our setup with ts-jest ensures that TypeScript files are understood and executed properly:
npm test
If our Greeter function maintains its composure under the scrutiny of TypeScript’s type system, our testing framework will confirm the success of our polite interactions. π
Observations from the Lab:
Type Declarations: TypeScript allows us to specify that our
greetfunction expects a string and will return a string, enhancing the predictability of our code.Test Environment Adaptation: By utilizing
ts-jest, we adapt our testing environment to understand TypeScript, ensuring our experiments can run without hindrance.Scientific Methodology in Code: TypeScript’s explicit type system encourages a methodological approach to coding, akin to a scientist’s rigor in their research, ensuring that every interaction is accounted for and every outcome is as expected.
And thus, we’ve transported our Greeter function into the TypeScript universe, demonstrating how type safety can coexist with the politeness and effectiveness of our code. Like meticulous scientists, we’ve planned our experiments, tested our hypotheses, and observed the outcomes, all within the structured and predictable environment provided by TypeScript. Here’s to coding with precision and greeting with warmth in all your future TypeScript endeavors! ππ«
Wrap up
Navigating through the JavaScript cosmos has been quite the journey! π You’ve danced with asynchronous patterns, mingled with callbacks, promises, and async/await, and even brought a sprinkle of emojis to your code. Alongside, you’ve harnessed the power of Jest for steadfast testing, ensuring your code’s resilience. This voyage isn’t just about mastering JavaScript; itβs a gateway to exploring other programming realms, each with its unique charm.
Transitioning from JavaScript’s lively ecosystem to TypeScript’s typed landscapes may feel like gearing up for a spacewalk. The initial setup is akin to preparing your spacecraftβrequiring more upfront work, especially for smaller missions. While the pace might feel slower at the launchpad, and the instructions more detailed, the journey yields significant rewards: safer code travels, enhanced navigation tools, and more maintainable code modules.
With JavaScript as your launchpad, you’re ready to venture into TypeScript’s asteroid belts of type safety or Python’s clear skies. Each programming language is a new frontier, offering novel solutions and creative possibilities.
So, buckle up for your next adventure! May your journey through the vast programming universe be filled with discovery, innovation, and, most importantly, fun code. Here’s to the countless opportunities that lie ahead on this interstellar coding odyssey! πβ¨