Others
Errors and Exception Handling
TypeScript allows you to catch and handle errors using standard JavaScript error handling mechanisms:
Try-Catch-Finally Blocks:
You can also handle different types of error:
Custom Error Types:
It is possible to specify more specific error by extending on the Error class
:
Mixin classes
Mixin classes allow you to combine and compose behavior from multiple classes into a single class. They provide a way to reuse and extend functionality without the need for deep inheritance chains.
Asynchronous Language Features
As TypeScript is a superset of JavaScript, it has built-in asynchronous language features of JavaScript as:
Promises:
Promises are a way to handle asynchronous operations and their results using methods like .then()
and .catch()
to handle success and error conditions.
To learn more: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
Async/await:
Async/await keywords are a way to provide a more synchronous-looking syntax for working with Promises. The async
keyword is used to define an asynchronous function, and the await
keyword is used within an async function to pause execution until a Promise is resolved or rejected.
To learn more: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
The following API are well supported in TypeScript:
Fetch API: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
Web Workers: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API
Shared Workers: https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker
WebSocket: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
Iterators and Generators
Both Interators and Generators are well supported in TypeScript.
Iterators are objects that implement the iterator protocol, providing a way to access elements of a collection or sequence one by one. It is a structure that contains a pointer to the next element in the iteration. They have a next()
method that returns the next value in the sequence along with a boolean indicating if the sequence is done
.
Generators are special functions defined using the function*
syntax that simplifies the creation of iterators. They use the yield
keyword to define the sequence of values and automatically pause and resume execution when values are requested.
Generators make it easier to create iterators and are especially useful for working with large or infinite sequences.
Example:
TypeScript also supports async iterators and async Generators.
To learn more:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator
TsDocs JSDoc Reference
When working with a JavaScript code base, it is possible to help TypeScript to infer the right Type by using JSDoc comments with additional annotation to provide type information.
Example:
Full documentation is provided to this link: https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html
From version 3.7 it is possible to generate .d.ts type definitions from JavaScript JSDoc syntax. More information can be found here: https://www.typescriptlang.org/docs/handbook/declaration-files/dts-from-js.html
@types
Packages under the @types organization are special package naming conventions used to provide type definitions for existing JavaScript libraries or modules. For instance using:
Will install the type definitions of lodash
in your current project.
To contribute to the type definitions of @types package, please submit a pull request to https://github.com/DefinitelyTyped/DefinitelyTyped.
JSX
JSX (JavaScript XML) is an extension to the JavaScript language syntax that allows you to write HTML-like code within your JavaScript or TypeScript files. It is commonly used in React to define the HTML structure.
TypeScript extends the capabilities of JSX by providing type checking and static analysis.
To use JSX you need to set the jsx
compiler option in your tsconfig.json
file. Two common configuration options:
- “preserve”: emit .jsx files with the JSX unchanged. This option tells TypeScript to keep the JSX syntax as-is and not transform it during the compilation process. You can use this option if you have a separate tool, like Babel, that handles the transformation.
- “react”: enables TypeScript’s built-in JSX transformation. React.createElement will be used.
All options are available here: https://www.typescriptlang.org/tsconfig#jsx
ES6 Modules
TypeScript does support ES6 (ECMAScript 2015) and many subsequent versions. This means you can use ES6 syntax, such as arrow functions, template literals, classes, modules, destructuring, and more.
To enable ES6 features in your project, you can specify the target
property in the tsconfig.json.
A configuration example:
ES7 Exponentiation Operator
The exponentiation (**
) operator computes the value obtained by raising the first operand to the power of the second operand. It functions similarly to Math.pow()
, but with the added capability of accepting BigInts as operands.
TypeScript fully supports this operator using as target
in your tsconfig.json file es2016
or larger version.
The for-await-of Statement
This is a JavaScript feature fully supported in TypeScript which allows you to iterate over asynchronous iterable objects from target version es2018.
New target meta-property
You can use in TypeScript the new.target
meta-property which enables you to determine if a function or constructor was invoked using the new operator. It allows you to detect whether an object was created as a result of a constructor call.
Dynamic Import Expressions
It is possible to conditionally load modules or lazy load them on-demand using the ECMAScript proposal for dynamic import which is supported in TypeScript.
The syntax for dynamic import expressions in TypeScript is as follows:
“tsc –watch”
This command starts a TypeScript compiler with --watch
parameter, with the ability to automatically recompile TypeScript files whenever they are modified.
Starting from TypeScript version 4.9, file monitoring primarily relies on file system events, automatically resorting to polling if an event-based watcher cannot be established.
Non-null Assertion Operator
The Non-null Assertion Operator (Postfix !) also called Definite Assignment Assertions is a TypeScript feature that allows you to assert that a variable or property is not null or undefined, even if TypeScript’s static type analysis suggests that it might be. With this feature it is possible to remove any explicit checking.
Defaulted declarations
Defaulted declarations are used when a variable or parameter is assigned a default value. This means that if no value is provided for that variable or parameter, the default value will be used instead.
Optional Chaining
The optional chaining operator ?.
works like the regular dot operator (.
) for accessing properties or methods. However, it gracefully handles null or undefined values by terminating the expression and returning undefined
, instead of throwing an error.
Nullish coalescing operator
The nullish coalescing operator ??
returns the right-hand side value if the left-hand side is null
or undefined
; otherwise, it returns the left-hand side value.
Template Literal Types
Template Literal Types allow to manipulate string value at type level and generate new string types based on existing ones. They are useful to create more expressive and precise types from string-based operations.
Function overloading
Function overloading allows you to define multiple function signatures for the same function name, each with different parameter types and return type. When you call an overloaded function, TypeScript uses the provided arguments to determine the correct function signature:
Recursive Types
A Recursive Type is a type that can refer to itself. This is useful for defining data structures that have a hierarchical or recursive structure (potentially infinite nesting), such as linked lists, trees, and graphs.
Recursive Conditional Types
It is possible to define complex type relationships using logic and recursion in TypeScript. Let’s break it down in simple terms:
Conditional Types: allows you to define types based on boolean conditions:
Recursion: means a type definition that refers to itself within its own definition:
Recursive Conditional Types combine both conditional logic and recursion. It means that a type definition can depend on itself through conditional logic, creating complex and flexible type relationships.
ECMAScript Module Support in Node
Node.js added support for ECMAScript Modules starting from version 15.3.0, and TypeScript has had ECMAScript Module Support for Node.js since version 4.7. This support can be enabled by using the module
property with the value nodenext
in the tsconfig.json file. Here’s an example:
Node.js supports two file extensions for modules: .mjs
for ES modules and .cjs
for CommonJS modules. The equivalent file extensions in TypeScript are .mts
for ES modules and .cts
for CommonJS modules. When the TypeScript compiler transpiles these files to JavaScript, it will create .mjs
and .cjs
files.
If you want to use ES modules in your project, you can set the type
property to “module” in your package.json file. This instructs Node.js to treat the project as an ES module project.
Additionally, TypeScript also supports type declarations in .d.ts files. These declaration files provide type information for libraries or modules written in TypeScript, allowing other developers to utilize them with TypeScript’s type checking and auto-completion features.
Assertion Functions
In TypeScript, assertion functions are functions that indicate the verification of a specific condition based on their return value. In their simplest form, an assert function examines a provided predicate and raises an error when the predicate evaluates to false.
Or can be declared as function expression:
Assertion functions share similarities with type guards. Type guards were initially introduced to perform runtime checks and ensure the type of a value within a specific scope. Specifically, a type guard is a function that evaluates a type predicate and returns a boolean value indicating whether the predicate is true or false. This differs slightly from assertion functions,where the intention is to throw an error rather than returning false when the predicate is not satisfied.
Example of type guard:
Variadic Tuple Types
Variadic Tuple Types are a features introduces in TypeScript version 4.0, let’s start to learn them by revise what is a tuple:
A tuple type is an array which has a defined length, and were the type of each element is known:
The term “variadic” means indefinite arity (accept a variable number of arguments).
A variadic tuple is a tuple type which has all the property as before but the exact shape is not defined yet:
In the previous code we can see that the tuple shape is defined by the T
generic passed in.
Variadic tuples can accept multiple generics make them very flexible:
With the new variadic tuples we can use:
- The spreads in tuple type syntax can now be generic, so we can represent higher-order operation on tuples and arrays even when we do not know the actual types we are operating over.
- The rest elements can occur anywhere in a tuple.
Example:
Boxed types
Boxed types refer to the wrapper objects that are used to represent primitive types as objects. These wrapper objects provide additional functionality and methods that are not available directly on the primitive values.
When you access a method like charAt
or normalize
on a string
primitive, JavaScript wraps it in a String
object, calls the method, and then throws the object away.
Demonstration:
TypeScript represents this differentiation by providing separate types for the primitives and their corresponding object wrappers:
- string => String
- number => Number
- boolean => Boolean
- symbol => Symbol
- bigint => BigInt
The boxed types are usually not needed. Avoid using boxed types and instead use type for the primitives, for instance string
instead of String
.
Covariance and Contravariance in TypeScript
Covariance and Contravariance are used to describe how relationships work when dealing with inheritance or assignment of types.
Covariance means that a type relationship preserves the direction of inheritance or assignment, so if a type A is a subtype of type B, then an array of type A is also considered a subtype of an array of type B. The important thing to note here is that the subtype relationship is maintained this means that Covariance accept subtype but doesn’t accept supertype.
Contravariance means that a type relationship reverses the direction of inheritance or assignment, so if a type A is a subtype of type B, then an array of type B is considered a subtype of an array of type A. The subtype relationship is reversed this means that Contravariance accept supertype but doesn’t accept subtype.
Notes: Bivariance means accept both supertype & subtype.
Example: Let’s say we have a space for all animals and a separate space just for dogs.
In Covariance, you can put all the dogs in the animals space because dogs are a type of animal. But you cannot put all the animals in the dog space because there might be other animals mixed in.
In Contravariance, you cannot put all the animals in the dogs space because the animals space might contain other animals as well. However, you can put all the dogs in the animal space because all dogs are also animals.
In TypeScript, type relationships for arrays are covariant, while type relationships for function parameters are contravariant. This means that TypeScript exhibits both covariance and contravariance, depending on the context.
Optional Variance Annotations for Type Parameters
As of TypeScript 4.7.0, we can use the out
and in
keywords to be specific about Variance annotation.
For Covariant, use the out
keyword:
And for Contravariant, use the in
keyword:
Template String Pattern Index Signatures
Template string pattern index signatures allow us to define flexible index signatures using template string patterns. This feature enables us to create objects that can be indexed with specific patterns of string keys, providing more control and specificity when accessing and manipulating properties.
TypeScript from version 4.4 allows index signatures for symbols and template string patterns.
The satisfies Operator
The satisfies
allows you to check if a given type satisfies a specific interface or condition. In other words, it ensures that a type has all the required properties and methods of a specific interface. It is a way to ensure a variable fits into a definition of a type
Here is an example:
Type-Only Imports and Export
Type-Only Imports and Export allows you to import or export types without importing or exporting the values or functions associated with those types. This can be useful for reducing the size of your bundle.
To use type-only imports, you can use the import type
keyword.
TypeScript permits using both declaration and implementation file extensions (.ts, .mts, .cts, and .tsx) in type-only imports, regardless of allowImportingTsExtensions
settings.
For example:
The following are supported forms:
using declaration and Explicit Resource Management
A using
declaration is a block-scoped, immutable binding, similar to const
, used for managing disposable resources. When initialized with a value, the Symbol.dispose
method of that value is recorded and subsequently executed upon exiting the enclosing block scope.
This is based on ECMAScript’s Resource Management feature, which is useful for performing essential cleanup tasks after object creation, such as closing connections, deleting files, and releasing memory.
Notes:
- Due to its recent introduction in TypeScript version 5.2, most runtimes lack native support. You’ll need polyfills for:
Symbol.dispose
,Symbol.asyncDispose
,DisposableStack
,AsyncDisposableStack
,SuppressedError
. - Additionally, you will need to configure your tsconfig.json as follows:
Example:
The code will log:
A resource eligible for disposal must adhere to the Disposable
interface:
The using
declarations record resource disposal operations in a stack, ensuring they are disposed in reverse order of declaration:
Resources are guaranteed to be disposed, even if subsequent code or exceptions occur. This may lead to disposal potentially throwing an exception, possibly suppressing another. To retain information on suppressed errors, a new native exception, SuppressedError
, is introduced.
await using declaration
An await using
declaration handles an asynchronously disposable resource. The value must have a Symbol.asyncDispose
method, which will be awaited at the block’s end.
For an asynchronously disposable resource, it must adhere to either the Disposable
or AsyncDisposable
interface:
The code logs:
The using
and await using
declarations are allowed in Statements: for
, for-in
, for-of
, for-await-of
, switch
.
Import Attributes
TypeScript 5.3’s Import Attributes (labels for imports) tell the runtime how to handle modules (JSON, etc.). This improves security by ensuring clear imports and aligns with Content Security Policy (CSP) for safer resource loading. TypeScript ensures they are valid but lets the runtime handle their interpretation for specific module handling.
Example:
with dynamic import: