DEV Community

Cover image for Detecting Errors Before Running Code With TypeScript
Max Kovalevskii
Max Kovalevskii

Posted on • Updated on • Originally published at byte.ski

Detecting Errors Before Running Code With TypeScript

This post is part of series and book about TypeScript. It will guide you from scratch to writing full TypeScript applications on Back End and Front End. The series is available as PDF eBook for free to everyone.

TypeScript Book (PDF)

The problem

In the previous post we talked about what is TypeScript and why should we use that. Now it's time to go to practice.

We need to know how to start using TypeScript in our JavaScript project. In examples of this post series, I will use mostly code that written in Node environment. It won't be any specific code that is understandable for only developers who worked with Node before. Because this material is about TypeScript I want to specify more on TypeScript itself.

Okay, let's start with an introduction to our first example. Here we have a very simple command-line application that works on Node. This example consists of one file. Let name it sum.js. When we execute this file by Node it will ask two questions in the Terminal - a value of argument X and Y. After typing these values the app will print the result of X + Y.

An example of this code is written with [[Node]]. If Node is not installed on your machine, check out the post about How To Install or Update Node by Using nvm (Node Version Manager).

Look at the code:

const readline = require("readline");

const rlInterface = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

// there is a simplified version of util.promisify method
const question = (message) => {
  return new Promise((resolve) => {
    rlInterface.question(message, (data) => {
      resolve(data);
    });
  });
};

function sum(a, b) {
  return a + b;
}

async function main() {
  try {
    const argX = await question("Type value of X: ");
    const argY = await question("Type value of Y: ");
    const result = sum(argX, argY);

    console.log(`Result: ${result}`);

    rlInterface.close();
  } catch (e) {
    console.error(e);
  }
}

main();
Enter fullscreen mode Exit fullscreen mode

Don't focus on module readline, methods createInterface and question. It's just a Node specific code that allows us to take data that the user types in the Terminal. Let's focus on functions sum and main. The function main is just an entry point of our small app.

Alright. Now let's test our app that it works correctly. To run the app use this command (if you already in the same folder as file sum.js there):

node sum.js
Enter fullscreen mode Exit fullscreen mode

The app is asking you to type the value of parameters X and Y. Let it be 7 and 2.

We expected that result will be 9 but the result is disappointed. The app prints:

72
Enter fullscreen mode Exit fullscreen mode

There is a bug. The thing is that the value that function question returns have type string, not number as it expected in function sum.

It is a typical problem with JavaScript type system. I would say, it is a JavaScript trademark. Probably, you could see memes and jokes about this problem.

It's all perfectly fine, but how can we avoid this problem? Of course, you can change function sum and do something like this (unary add operator):

function sum(a, b) {
  return +a + +b;
}
Enter fullscreen mode Exit fullscreen mode

But don't you think that this looks like a bad solution? It seems like that we try to use a patch to hide a hole in the tearing jacket. Instead of this, we can put on a new jacket that won't have holes (or maybe less than the previous one) - TypeScript.

The solution

Installing TypeScript

To install TypeScript globally on your machine let's use npm:

npm install -g typescript
Enter fullscreen mode Exit fullscreen mode

Alright. Now we need to check that TypeScript was installed. Type this command in the Terminal:

tsc --version
Enter fullscreen mode Exit fullscreen mode

It should print you something like this:

Version 4.2.4
Enter fullscreen mode Exit fullscreen mode

It means that TypeScript was successfully installed on our machine. What is tsc command? It is a TypeScript compiler. As mentioned in the previous post, TypeScript compiler is a tool, or program, that turns the TypeScript code into JavaScript code. We need this feature because we will execute this compiled JavaScript code by Node.

From JavaScript to TypeScript

Alright. To solve the problem we need to write the same code as before but in TypeScript. Let's change extension of the JavaScript file sum.js to TypeScript file extension - .ts. Just rename the file from sum.js to sum.ts and let's see that we will have it in the editor.

We just renamed our file but there are already some changes in the editor (I use Visual Studio Code):

image

We have several lines with red underlining which means that there are TypeScript errors. There are also two dashed borders on line 11 - TypeScript warnings. But, why don't we just ignore all this stuff and execute our code? Let's try it.

For executing this file now we must first compile it by TypeScript compiler.

Run this command in the Terminal to compile TypeScript file sum.ts:

tsc sum.ts
Enter fullscreen mode Exit fullscreen mode

Oops! After running this command we will see that our code cannot be compiled because of the errors that were marked in the editor.

image

And there is a thing. TypeScript won't allow you to compile the code that contains errors.

Fixing the code

To compile and execute this file we need to fix the code in the file. Let's see what the errors we have there.

The first four problems are about the same thing.

error TS2468: Cannot find global value 'Promise'.

sum.ts:3:18 - error TS2580: Cannot find name 'require'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`.

3 const readline = require("readline");
Enter fullscreen mode Exit fullscreen mode

TypeScript tries to understand the types of modules that we use in the code - readline. To help TypeScript know the types of the modules we need to install type definitions. You will learn about it more in the next posts. For now, let's just say that type definitions is a special notation that helps TypeScript to know types of code that were originally written in JavaScript.

Let's install it as TypeScript tells us:

npm install --sade-dev @types/node
Enter fullscreen mode Exit fullscreen mode

Then, try to compile file sum.ts again:

tsc sum.ts
Enter fullscreen mode Exit fullscreen mode

Great! We don't have any errors and successfully compiled our TypeScript file into JavaScript. You should see that there is a new file called sum.js in the same folder as sum.ts. No, this is not the file that we created before. This file contains compiled JavaScript code of sum.ts file.

If you open this file, well... You may be afraid. There is no our code at all! Don't jump to conclusions. It still the same code as we wrote in sum.ts but it transformed into a form that is more understandable for runtime environment (in our case - Node, also it might be Web browser).

Okay, let's execute our code again. But note that we have to execute compiled code, i.e. sum.js, not sum.ts:

node sum.js
Enter fullscreen mode Exit fullscreen mode

Let's type new values: 13 and 7. We will see the wrong result, again.

137
Enter fullscreen mode Exit fullscreen mode

But you said that we will solve this problem by using TypeScript and we will catch the errors before executing the file! Well, there is another thing of TypeScript that you need to remember. You wanna help? Help yourself!. In our case, it means that we have to say to TypeScript where the problem can be.

Use types to prevent bug

Let's describe our problem in the code. The function question returns a value that has a type string. But we don't know about it before executing the file. Because we don't know it we bravely put the values that function question returns into a parameter of function sum. The function sum expected that values will have type number and it worked with them as if they were numbers.

So, firstly, we need to say to TypeScript that function question returns string type. Let's do it!

To specify what type of value function returns we should write this code:

const question = (message): string => {
  return new Promise((resolve) => {
    rlInterface.question(message, (data) => {
      resolve(data);
    });
  });
};
Enter fullscreen mode Exit fullscreen mode

Hmm. We specified the type of returned value, but TypeScript shows that there is an error:

image

An error sounds like this:

Type 'Promise<unknown>' is not assignable to type 'string'.ts(2322)
Enter fullscreen mode Exit fullscreen mode

It means that we cannot just specify the type string as a type of returned value of function question because the function question is an asynchronous function and returns Promise.

Alright. To specify the type in this kind of function we just need to specify it like Promise<your_type> as TypeScript writes to us in the text of the error.

Let's fix that:

const question = (message): Promise<string> => {
  return new Promise((resolve) => {
    rlInterface.question(message, (data) => {
      resolve(data);
    });
  });
};
Enter fullscreen mode Exit fullscreen mode

Okay. Did we tell TypeScript there might be a problem? Not yet. The next step is to specify the types of parameters of the function sum.

To specify types of function's parameters we should write this code:

function sum(a: number, b: number) {
  return a + b;
}
Enter fullscreen mode Exit fullscreen mode

Let's look at the function main where functions question and sum are calling:

image

This is it!. This is the error that helps us to fix the bug with the wrong result that prints in the Terminal. Now, if we would try to compile file sum.ts we will see the error.

To run our program in one file use this command:

tsc sum.ts && node sum.js
Enter fullscreen mode Exit fullscreen mode

We will see:

image

All we have to do is to write a code that converts values from string type to number:

async function main() {
  try {
    const argX = await question("Type value of X: ");
    const argY = await question("Type value of Y: ");
    + const x = Number(argX);
    + const y = Number(argY);
    - const result = sum(argX, argY);
    + const result = sum(x, y);

    console.log(`Result: ${result}`);

    rlInterface.close();
  } catch (e) {
    console.error(e);
  }
}
Enter fullscreen mode Exit fullscreen mode

Let's see the result of executing our program:

image

Congratulations! You solve the problem and prevent the bug by using TypeScript!

TypeScript compiler is a very configurable tool. In the next post of the series we deep dive into configuring TypeScript.

Do you like the material? Please, subscribe to my email newsletter to stay up to date.

subscribe

Top comments (0)