Categories
TypeScript Best Practices

TypeScript Best Practices — Iteration, Promises, and eval

Spread the love

TypeScript is an easy to learn extension of JavaScript. It’s easy to write programs that run and does something. However, it’s hard to account for all the uses cases and write robust TypeScript code.

In this article, we’ll look at the best practices to following when writing code with TypeScript, including replacing for-in loops with better alternatives.

Promise code should also be useful.

eval-like Methods also shouldn’t be used.

No Iteration Over an Array with a for-in Loop

The for-in loop isn’t all that useful now that there are the for-of loops and methods to get object keys.

Also, it iterates over the prototypes of our object, which we probably don’t want.

The order of iteration also isn’t guaranteed.

Therefore, we shouldn’t use it in our code.

Instead of writing:

for (const x in [1, 2, 3]) {
  console.log(x);
}

We write:

for (const x of [1, 2, 3]) {
  console.log(x);
}

The for-in loop loops over the indexes, while the for-of loop loops over the entries.

Don’t Use eval-Like Methods

In JavaScript and by extension, TypeScript, both have methods that take strings and run them as code.

There’s the eval method which runs code from a string.

The Function constructor also returns a function from a string.

Also, setTimeout and setInterval functions can both run code from a string.

This prevents any optimization from being done since the code is in a string.

Also, it creates a big security flaw since anyone can input a string with code and run it potentially.

Therefore, we shouldn’t run any function that allows us to run code from string.

If we use setTimeout or setInterval , we should pass in a callback instead of a string.

For instance, instead of writing:

setTimeout('alert(`foo`);', 100);

or:

const add = new Function('a', 'b', 'return a + b');

We write:

setTimeout(() => {
  alert(`foo`);
}, 100);

or:

setInterval(() => {
  alert(`foo`);
}, 10000);

and don’t use the other functions.

No Explicit Type Declarations for Variables or Parameters Initialize to a number, string, or boolean

We don’t need types for anything that’s been explicitly assigned a number, string, or boolean.

This is because it’s obvious from the code what they are.

Therefore, instead of writing:

const a: number = 10;

We write:

const a = 10;

This applies to any other primitive values like string or boolean.

If the return type is obvious, then we can also skip the type annotation.

So, instead of writing:

const a: number = Number('1');

We write:

const a = Number('1');

This also applies to regexes. If we have a regex literal, then we don’t nee the annotation.

Instead of writing:

const a: RegExp = /foo/;

We write:

const a = /foo/;

Use new and constructor in Valid Ways

We should use new and constructor in valid ways.

Therefore, we shouldn’t use them for creating new class instances or as constructor functions respectively.

For instance, instead of writing:

class C {
  new(): C;
}

We write:

class C {
  constructor(){
    //...
  }
}

const c = new C();

Constructors should only be in classes.

We can also specify new as a signature in interfaces:

interface I {
  new (): C;
}

Don’t Use Promises in Places that aren’t Designed to Handle Them

We shouldn’t use promises in places that aren’t designed to handle them.

For instance, we shouldn’t put them in if statements or loops.

So instead of writing:

const promise = Promise.resolve('foo');

if (promise) {
  // Do something
}

or:

const promise = Promise.resolve('foo');

while (promise) {
  // Do something
}

or:

[1, 2, 3].forEach(async value => {
  await foo(value);
});

or:

new Promise(async (resolve, reject) => {
  await doSomething();
  resolve();
});

We should write:

const promise = Promise.resolve('foo');

if (await promise) {
  // Do something
}

or:

const promise = Promise.resolve('foo');

while (await promise) {
  // Do something
}

or:

for (const value of [1, 2, 3]) {
  await foo(value);
}

We need to put await in the right places so that we get the resolved value properly.

Also, the following isn’t valid:

new Promise(async (resolve, reject) => {
  await doSomething();
  resolve();
});

because it’s redundant to have a promise inside a promise.

We can move the await doSomething() outside our promise callback.

Conclusion

Promises should be used in a useful manner or they shouldn’t be used at all.

eval-like functions shouldn’t be used because they’re dangerous.

for-in loops should be replaced with better alternatives.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *