Categories
TypeScript

Introduction to TypeScript Interfaces — Indexable Types

The big advantage of TypeScript over plain JavaScript is that it extends the features of JavaScript by adding functionality that ensures the type safety of our program’s objects. It does this by checking the shape of the values that objects take on.

Checking the shape is called duck typing or structural typing. Interfaces are one way to fill the role naming data types in TypeScript. It’s very useful for defining contracts within our code in TypeScript programs. In the last article, we looked at how to define a TypeScript interface and adding required and optional properties to it. In this article, we’ll continue to look at other properties of TypeScript interfaces like indexable types.

Indexable Types

We can define indexable types for data like arrays. Any object that uses bracket notation like arrays and dynamic object types can be designated with indexable types. Indexable types have an index signature that describes the types that we can use as an index for our object, alongside the return type for the corresponding index. It’s very handy for designating the types for dynamic objects. For example, we can design an array that only accepts strings like in the following code:

interface NameArray {
    [index: number]: string;
}

let nameArray: NameArray = ["John", "Jane"];
const john = nameArray[0];
console.log(john);

In the code above, we defined the NameArray interface that takes in a index that is of type number as the index signature, and the return type of the corresponding index signature is a string. Then when we designate a variable with the NameArray type then we can use the index to get the entries of the array. However, with this code, the array methods and operators aren’t available since we only have the [index: number] index signature and nothing, so the TypeScript compiler isn’t aware that it’s an array even though it looks like one to the human eye.

Index signatures support 2 types. They can either be strings or numbers. It’s possible to support both types of indexes, but the type returned from a numeric indexer must be a subtype of the one returned by the string indexes. This is because JavaScript will convert numeric indexes to strings when it’s trying to accessing entries or properties with numeric properties. This ensures that it’s possible to get different results returned for the same index.

For example, the following code would give us an error from the TypeScript compiler:

class Animal {
  name: string = '';
}

class Cat extends Animal {
  breed: string = '';
}

interface Zoo {
    [x: number]: Animal;
    [x: string]: Cat;
}

If we try to compile the code above, we would get “Numeric index type ‘Animal’ is not assignable to string index type ‘Cat’.(2413)”. This is because we have Cat as a return type of the string index, which is a subtype of Animal. We can’t have this since if we have 2 index signatures with different types, then the supertype must be the return type of the index signature with the string type, and the index signature with the number type must have the subtype of the of returned by the one with the string index signature. This means that if we flip the return types around, then code will be compiled and run:

class Animal {
  name: string = '';
}

class Cat extends Animal {
  breed: string = '';
}

interface Zoo {
    [x: number]: Cat;
    [x: string]: Animal;
}

Since Animal is a supertype of Cat, we must have Animal as the return type of the string index signature, and the Cat type as the return type of the number index signature.

Photo by Nathalie SPEHNER on Unsplash

Index signatures enforce that all normal property matches their return type in addition to the ones that are accessed by the bracket notation since in JavaScript obj.prop and obj['prop'] are the same. This means that if we have the following code:

interface Dictionary {
  [x: string]: string;
}

let dict: Dictionary = {};
dict.prop = 1;

Then we would get the error “Type ‘1’ is not assignable to type ‘string’.(2322)” since we specified that all properties are strings in the variable that has the Dictionary type. If we want to accept other types in the properties of our objects, we have to use union types. For example, we can write the following interface to let the properties of the object with the given type accept both string and numbers as values:

interface Dictionary {
  [x: string]: string | number;
  num: number;
}

let dict: Dictionary = { num: 0 };

In the example above, we accept both string and number as both types of our values. So we add a property with a number type without the TypeScript compiler rejecting the code with an error. Therefore, in the last line of the code above, we can add a num property to the object with the value 0.

We can also make an index signature readonly so that we can prevent assignment to their indices. For example, we can mark an index signature as read only with the following code:

interface Dictionary {
  readonly [x: string]: string;
}

let dict: Dictionary = {'foo': 'foo'};

Then when we try to assign another value to dict['foo'] like in the code below, the TypeScript compiler will reject the code and won’t compile it:

interface Dictionary {
  readonly [x: string]: string;
}

let dict: Dictionary = {'foo': 'foo'};
dict['foo'] = 'foo';

If we try to compile the code above, we’ll get the error “Index signature in type ‘Dictionary’ only permits reading.(2542)”. This means that we can only set the properties and values of a read only property when the object is being initialized, but subsequent assignments will fail.

Conclusion

Indexable types are very handy for defining the return values of the properties of dynamic objects. It takes advantage of the fact that we can access JavaScript properties by using the bracket notation. This is handy for properties that have invalid names if defined without the bracket notation or anything that we want to be able to be accessed by the bracket notation and we want type checking on those properties or entries. With indexable types, we make sure that properties that are assigned and set by the bracket notation have the designated types.

Also, this also works for regular properties since bracket notation is the same as the dot notation for accessing properties. Also, we can designate index signatures as readonly so that they can be written to when the object with a type with indexable types is initialized but not after. If we have both number and string index signatures, then the string indexable signature must have the return type that’s the super-type of the one with the number index signature so that we get consistent types for objects when we access properties.

Categories
TypeScript

Introduction to TypeScript Interfaces — Object Literals and Function Types

The big advantage of TypeScript over plain JavaScript is that it extends the features of JavaScript by adding functionality that ensures the type safety of our program’s objects. It does this by checking the shape of the values that objects take on.

Checking the shape is called duck typing or structural typing. Interfaces are one way to fill the role naming data types in TypeScript. It’s very useful for defining contracts within our code in TypeScript programs. In the last article, we looked at how to define a TypeScript interface and adding required and optional properties to it. In this article, we’ll continue from the previous article and look at other properties of TypeScript interfaces.

Excess Property Checks

Object properties get extra checks when they’re being assigned to a variable with the type designated by an interface. This also applies to object literals that we pass into functions as arguments. For example, the following code wouldn’t be compiled by the TypeScript compiler and give us an error:

interface Person{
  name: string
}

const greet = (person: Person) => {
  console.log(`Hello, ${person.name}`);
}

greet({ name: 'Joe', foo: 'abc' });

The excess property check done by the TypeScript compiler will reject the code since we have an extra foo property that isn’t defined in the Person interface, so add it in the object in the parameter would fail because of TypeScript’s excess property checks for object literals. Assigning the same object literal to a variable will also fail. For example, if we have the following code:

interface Person{
  name: string
}
const greet = (person: Person) => {
  console.log(`Hello, ${person.name}`);
}
const person: Person = { name: 'Joe', foo: 'abc' };
greet(person);

We would get the error “Type ‘{ name: string; foo: string; }’ is not assignable to type ‘Person’. Object literal may only specify known properties, and ‘foo’” if we try to compile the code with the TypsScript compiler or look at the code at a text editor that supports TypeScript. However, we can use the type assertion operator as to designate the type of the object literal as we like it. So if we’re sure that the object literal if of the type Person even though it has a foo property in it, we can write the following code:

interface Person{
  name: string
}
const greet = (person: Person) => {
  console.log(`Hello, ${person.name}`);
}
const person: Person = { name: 'Joe', foo: 'abc' } as Person;
greet(person);

With the code above, the TypeScript compiler won’t complain of any issues. It’ll just assumes that the object literal is of type Person even though it has a foo property. However, we do have some properties that are dynamic or may only sometimes appear, we can also add a dynamic property to our TypeScript interfaces like in the following code:

interface Person{
  name: string,
  [prop: string]: any
}

const greet = (person: Person) => {
  console.log(`Hello, ${person.name}. ${person.age ? `You're ${person.age} years old.` : ''}`);
}

const person: Person = { name: 'Jane', age: 20 };
greet(person);

In the code above, we added:

[prop: string]: any

to our Person interface. The line above means that the type Person can have any other property other than name . The property name is a string, which is the case for the dynamic property names in JavaScript, and these dynamic properties can take on any value since specified the any type for the dynamic property. As we can see, we have the following line:

const person: Person = { name: 'Jane', age: 20 };

where our object literal has the age property but it’s not explicitly defined in our interface definition. This is because we have the dynamic property after the name property. The [prop: string] is called the index signature.

We can also get around the excess property check for object literals by assigning a variable to another variable. For example, if we have the following code:

interface Person{
  name: string
}

const greet = (person: Person) => {
  console.log(`Hello, ${person.name}. ${person.age ? `You're ${person.age} years old.` : ''}`);
}

const person: Person = { name: 'Jane', age: 20 };
greet(person);

which wouldn’t compile and run because of the excess property check, we can get around it by assigning the person constant to a new variable or constant that doesn’t have a type designated to it like we do below:

interface Person{
  name: string
}

const greet = (person: Person) => {
  console.log(`Hello, ${person.name}`);
}

const person = { name: 'Jane', age: 20 };
greet(person);

The person constant doesn’t have a type designated to it so the excess property check for object literals won’t be run.

The excess property check is recommended to be enforced for simple objects like the ones we have above. For more complex, dynamic objects, we can use the ways we outline above to get around the checks to get the code running. However, do be aware that most excess property errors are actually typos in our code, so they’re legitimate bugs that should be fixed.

Photo by Max Baskakov on Unsplash

Function Types

With TypeScript interfaces, we can also define the signature of functions by designating the data type for each parameter and the return type of the function. This prevents us from passing in parameters that have the wrong data type or forgetting to pass in arguments into our function calls, and also ensures that our function always have the same return type and we won’t be returning things that we don’t expect in our code.

We can define a interface for designating the parameter and return data types of our function, and the function signature like we do in the code below:

interface GreetFn{
  (name: string, age: number): string
}

const greet: GreetFn = (name: string, age: number) => {
  return `Hello, ${name}. You're ${age} years old`;
}

console.log(greet('Jane', 20));

The code above has the function greet that follows the function signature defined on the left side of the colon in the GreetFn interface and the return data type on the right side of the interface, so the code will run and produce output from the console.log statement in the last line. We should get ‘Hello, Jane. You’re 20 years old’. If we designate our greet function with the type GreetFn but our function signature or return type stray away from the ones designated in the GreetFn interface then we’ll get errors. For example, if we have:

interface GreetFn{
  (name: string, age: number): string
}
const greet: GreetFn = (name: string, age: number, foo: any) => {
  return `Hello, ${name}. You're ${age} years old`;
}
console.log(greet('Jane', 20));

Then we’ll get the error message “Type ‘(name: string, age: number, foo: any) => string’ is not assignable to type ‘GreetFn’.(2322)“ since our parameter list doesn’t match the signature listed in the interface. Likewise, if our function’s return type doesn’t match the one we defined in the interface, we’ll also get an error. For example if we have the following code:

interface GreetFn{
  (name: string, age: number): string
}
const greet: GreetFn = (name: string, age: number) => {
  return 0;
}
console.log(greet('Jane', 20));

We’ll get the error “Type ‘(name: string, age: number) => number’ is not assignable to type ‘GreetFn’. Type ‘number’ is not assignable to type ‘string’.” This means that the greet function must return a string since we specified that the type of the greet function is GreetFn .

Function parameters are checked one at a time, so the TypeScript compiler infers the position of the type of a parameter by its position even though no types are designated by us when we define our function. For example, the following will still work even though we didn’t specified the type of our parameters explicitly:

interface GreetFn{
  (name: string, age: number): string
}
const greet: GreetFn = (name, age) => {
  return `Hello, ${name}. You're ${age} years old`;
}
console.log(greet('Jane', 20));

If we pass in something with the wrong data type according to the interface we defined like in the code below, we’ll get an error:

interface GreetFn{
  (name: string, age: number): string
}
const greet: GreetFn = (name, age) => {
  return `Hello, ${name}. You're ${age} years old`;
}
console.log(greet('Jane', ''));

When we try to compile the code above, we’ll get the error “Argument of type ‘“”’ is not assignable to parameter of type ‘number’.(2345)“. This means that TypeScript is smart enough to infer the type by its position. Type inference is also done for the return type, so if we write the following code:

interface GreetFn{
  (name: string, age: number): string
}
const greet: GreetFn = (name, age) => {
  return 0;
}
console.log(greet('Jane', 20));

Then we’ll get the error “Type ‘(name: string, age: number) => number’ is not assignable to type ‘GreetFn’. Type ‘number’ is not assignable to type ‘string’.(2322)” so the code won’t compile.

Excess property checks for object literals are useful since it’s much harder for us to add wrong properties or typos into our code when we’re assigning object literals or passing them in as arguments of functions. We can get around it with type assertion or assigning to a variable with different types or no types. We can also define interfaces for functions to define the expected parameters for a function and also the expected return type for them.

Categories
JavaScript Basics

Introduction to Web Workers

To run tasks that take a long time to complete, we need a way to run them in the background.

Web workers are the way to do that. They live in their own context within the browser. Therefore, they don’t have access to the DOM and have their own global variable, self.

A web worker can’t access the window object, but we still use many things that are parts of it like WebSockets and IndexedDB.

In this article, we’ll look at how to create a basic web worker that can send messages between workers and other scripts.


Characteristics of Web Workers

Web workers allow us to send messages between them and other scripts. Non-worker scripts can listen to messages that are emitted by workers by attaching handlers for messages.

To send messages, we use the postMessage() method. The message can be accessed via the Message event’s data property. Message event objects are passed into the event handler of the onmessage handler.

Workers can spawn new workers, as long as they are hosted within the same origin as the parent page. Workers may also use XmlHttpRequest for network I.O, with the exception that the responseXML and channel attributes on XMLHttpRequest always return null.

In addition to dedicated workers, there are also other kinds of workers:

  • Shared workers are workers that can be used by multiple scripts in different windows, IFrames, etc, as long as they’re in the same domain as the worker. They are more complex than dedicated workers in that scripts must communicate via an active port.
  • Service workers act as proxy services that sit between web apps, the browser, and the network. They are intended to create effective offline experiences. They intercept network requests and take appropriate actions based on whether the network is available.
  • Chrome workers are a Firefox-only type of worker that we can use when developing add-ons and want to use workers in extensions.
  • Audio workers provide the ability to direct scripted audio processing to be done inside a web worker context.

Photo by David Siglin on Unsplash


Creating a Simple Dedicated Worker

We can create a dedicated worker by creating a simple worker script and then have other scripts communicate with it.

For example, we can create a dedicated worker that takes values from two inputs and then compute the result by adding them and sending it back to the screen.

First, we create the HTML for getting the input values as follows:

Then, in main.js, we add keyup event handlers to the inputs to send the values to our dedicated worker as follows:

We also add the onmessage event handler to receive the message from the worker, which we’ll add to worker.js to get the message back from it in the following piece of code:

worker.onmessage = e => {  
  result.textContent = e.data;  
};

Finally, we create the worker code in worker.js as follows:

The code above gets the message from main.js, where we called postMessage. Whatever we passed to postMessage will be in the e parameter and we can get the data from there.

Then, in this file, we get the two numbers we passed in with the data property on the first line of the function.

We then check if both numbers are numbers and if they are, we add them together and then send the sum back to main.js with postMessage after we compute the sum and put the result in the workerResult string.

In the end, we should get something like the following:

As long as the dedicated worker script is in the same domain as our other scripts, it’ll run.

The Worker constructor also takes an option object with the following options:

  • type: A string specifying the type of worker to create. The value can be classic or module and defaults to classic.
  • credentials: A string specifying the type of credentials to use for the worker. The value can be omit (no credentials required), same-origin, or include. If it’s not specified, the type is class, then omit is the default.
  • name: A string specifying the identifying name for the DedicatedWorkerGlobalScope representing the scope of the worker. It’s mainly used for debugging.

The constructor will throw a SecurityError if it’s not allowed to start workers, like if the URL is invalid or the same-origin policy is violated.

  • NetworkError is raised if the MIME type of the worker script is incorrect.
  • SyntaxError is raised is the worker’s URL can’t be parsed.

Creating dedicated workers is simple. They let us run background tasks. Communication between workers and other scripts is by sending messages back and forth.

Web workers have their own context so they can’t use the window object or access the DOM.

Categories
JavaScript JavaScript Basics

Handy JavaScript Shorthands

With the latest versions of JavaScript, the language has introduced more syntactic sugar. In this article, we’ll look at handy shortcuts that are easy to read from versions of JavaScript new and old.

In this article, we’ll look at the ternary operator, declaring multiple variables, arrow functions, default parameter values, and more.

Ternary Operator

We can write an if...else statement in a concise way by using the ternary operator.

Instead of writing:

const x = 20;
let grade;

if (x >= 50) {
  grade = "pass";
} else {
  grade = "fail";
}

We can write:

const x = 20;
let grade = (x >= 50) ? "pass" : "fail";

They both check if x is bigger than or equal to 50 then assign the string 'pass' if it’s true and fail otherwise.

We can also write nested if statement with the ternary operator as follows:

const x = 20;
let grade = (x >= 50) ? "pass" : (x >= 25) ? "good fail" : 'bad fail';

This is the same as:

const x = 20;
let grade;
if (x >= 50) {
  grade = "pass";
} else {
  if (x >= 25) {
    grade = "good fail";
  } else {
    grade = "bad fail";
  }
}

Setting Default Value

We can set a default value if a variable is falsy by writing:

let x;
let y = x || 10;

This is the same as:

let x;
let y;
if (x === undefined || x === null || x === 0 || x === '' || isNaN(x)) {
  y = 10;
}

Because x || 10 means that if x is falsy, which means that x is undefined, null, 0, empty string, or NaN, then we assign 10 to y, which is the same as:

if (x === undefined || x === null || x === 0 || x === '' || isNaN(x)) {
  y = 10;
}

Shorthand for Declaring Multiple Variables

We can declare multiple variables by writing:

let x = y = z = 5;

This is the same as writing:

let x = 5;
let y = 5;
let z = 5;

It works by first assigning 5 to z, then the value of z to y, and finally the value of y to x .

If Truthy Shorthand

A shorthand for checking if something is truthy is JavaScript, which is anything other than undefined, null, 0, empty string, or NaN, is the following:

if (x){
 console.log('x is truthy')
}

The code above checks if x is truthy, then we execute the console.log if it is.

For…Of Loop Shorthand

Since ES6, we can use the for...of loop to loop through variables in an array or array-like object, which includes things like Maps, Sets, the arguments object, generators, iterators, and any object with the [Symbol.iterator] method.

We can write:

let fruits = ['apple', 'orange', 'grape'];
for (let fruit of fruits) {
  console.log(fruit);
}

This is cleaner than using a regular for loop with indexes, and it also works other iterable objects. For example, we can use it with generators:

let fruits = function*() {
  yield 'apple';
  yield 'orange';
  yield 'fruits';
}

for (let fruit of fruits()) {
  console.log(fruit);
}

Photo by Robin Mantz on Unsplash

Array.forEach

We can use the Array.forEach method to loop through an array, although it’s slower than loops.

To use it, we can write something like the following code:

let fruits = ['apple', 'orange', 'grape'];

fruits.forEach((fruit, index) => console.log(fruit));

Decimal Base Exponents

We can specify exponential numbers instead of writing out the whole number out with all the trailing zeroes.

For example, if we have:

1e0

for 1

1e1

for 10

1e2

for 100

1e3

for 1000 and so on.

Digit Separators

The latest browsers let us use an underscore to separate digits to make them easy to read. For example, we can write

100_000_000

for 100 million. Underscores can be placed anywhere we choose.

Object Property Shorthand

Instead of writing:

const foo = 1,
  bar = 2;
const obj = {
  foo: foo,
  bar: bar
};

We can write:

const foo = 1,
  bar = 2;
const obj = {
  foo,
  bar
};

The 2 pieces of code are exactly the same.

Arrow Functions

If an arrow function is only one line, then we don’t need the braces and we can return a value from it without using the return keyword.

For example:

() => 1

is the same as:

() => {
  return 1
}

We can use arrow functions if we don’t care about the value of this since arrow functions don’t change the this value inside the function.

Default Parameter Values

We can specify default parameter values in ES6. For example, we can write:

const add = (a = 1, b = 2) => {
  return a + b
}

This is the same as:

const add = (a, b) => {
  if (typeof a === 'undefined') {
    a = 1;
  }

  if (typeof b === 'undefined') {
    b = 1;
  }
  return a + b
}

The shorthands above are mostly from ES6. This version of JavaScript provides lots of handy shortcuts which lets us write code easier and reading just as easy.

for...of loop is very useful since it can loop through arrays and array-like objects. No other loops can do that.

Digit separators are more recent and available in the latest browsers only.

Categories
JavaScript JavaScript Best Practices

JavaScript Best Practices — Modules and Identifiers

To make code easy to read and maintain, we should follow some best practices.

In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.

Minimum and Maximum Identifier Lengths

We should make identifier descriptive so they definitely shouldn’t be too short.

It also shouldn’t be too long.

If it’s too short, it’s probably not descriptive enough.

If it’s too long, then it has redundant information and takes up too much space.

So we shouldn’t write:

const x = 5;

since we don’t know what x is.

But we can write something like const numApples = 1; .

Location of Arrow Function Bodies with Implicit Returns

We should put arrows in a logical location.

For instance, we can write:

(foo) => bar;

or

(foo) => (bar);

or

(foo) => bar => baz;

They are conventional and logical.

It’s better than these examples:

(foo) =>  
  bar;

or:

(foo) =>  
  (bar);

The first examples are more compact than these ones.

Default Imports

We’ve have a default export for a default import to work.

For instance, we should write:

foo.js

export default function () { return 100 }

Then we should import it by writing:

import foo from './foo';

For CommonJS modules, we can write:

foo.js

module.exports = function () { return 100 }

Then we can import it by writing:

const foo = require('./foo');

Repeated Imports and Exports

We can only have one default export in each module.

For instance, we can’t write:

export default class Foo { /*...*/ } 
export default class Bar { /*...*/ }

We have to remove one of them.

If we remove one:

export default class Foo { /*...*/ }

Then we can write:

import Foo from './foo';

Export Statements that Come Before Other Statements

We shouldn’t have export statements that come before other statements.

For instance, we don’t want to fix statements like:

const bool = true
export default bool
const str = 'foo'

Moving all exports to the bottom would make it easier to read.

Use of File Extension within the Import Path

When we write imports, we don’t need an extension for JavaScript files.

For instance, instead of writing:

import bar from './foo/bar.js';

We write:

import bar from './foo/bar';

However, we should put the extension for JSON import.

For instance, we can write:

import bar from './bar.json';

We may also do the same thing for JavaScript files that don’t have the .js extension.

For instance, we can write:

import Component from './Component.jsx';

for JSX files.

Putting Imports First

We should put import statements first.

For instance, we should put them at the top.

Instead of writing:

import foo from './foo'
doSomething(foo)
import bar from './bar'

We write:

import foo from './foo';  
import bar from './bar';
doSomething(foo);

Grouping Exports

We can group exports together to make finding them easier.

For instance, we can write:

const first = 'foo';  
const second = 'bar';
export {  
  first,  
  second,  
}

With CommonJS modules, we can write:

const test = {};  
test.foo = true  
test.bar  = true
module.exports = test;

We also grouped the exports together.

Named Exports

We can import named module members in various ways.

Given that we have:

export const foo = "foo"

We can write:

import { foo } from './foo'

Also, we can write:

export { foo as bar } from './foo'

to export it as bar .

We can also import it as bar:

import { foo as bar } from './foo'

Newline After Import

After a group of imports, we can put a new line to separate the imports from the rest of the code.

For instance, we can write:

import defaultExport from './foo'  
import { bar } from 'bar'  
  
const foo = 'bar';

or:

const foo = require('./foo')  
const bar = require('./bar')  
  
const baz = 1;

for CommonJS modules.

No Absolute Paths

We shouldn’t use absolute paths for imports because it’ll only work on the current computer’s current folder strucutre.

If we move it anywhere, it won’t work.

So we shouldn’t write:

import f from '/foo';  
import bar from '/some/path';

or:

const f = require('/foo');  
const bar = require('/some/path');

Instead, we write:

import f from './foo';  
import bar from './some/path';

or:

const f = require('./foo');  
const bar = require('./some/path');

Conclusion

We should write our imports and exports properly.

Never import with absolute paths.

And we should group them together.

Identifier lengths shouldn’t be too long or short.