TypeScript 2.3
Generators and Iteration for ES5/ES3
First some ES2016 terminology:
Iterators
ES2015 introduced Iterator
, which is an object that exposes three methods, next
, return
, and throw
, as per the following interface:
tsinterface Iterator<T> { next(value?: any): IteratorResult<T>; return?(value?: any): IteratorResult<T>; throw?(e?: any): IteratorResult<T>; }
This kind of iterator is useful for iterating over synchronously available values, such as the elements of an Array or the keys of a Map.
An object that supports iteration is said to be “iterable” if it has a Symbol.iterator
method that returns an Iterator
object.
The Iterator protocol also defines the target of some of the ES2015 features like for..of
and spread operator and the array rest in destructuring assignmnets.
Generators
ES2015 also introduced “Generators”, which are functions that can be used to yield partial computation results via the Iterator
interface and the yield
keyword.
Generators can also internally delegate calls to another iterable through yield *
. For example:
tsfunction* f() { yield 1; yield* [2, 3]; }
New --downlevelIteration
Previously generators were only supported if the target is ES6/ES2015 or later.
Moreover, constructs that operate on the Iterator protocol, e.g. for..of
were only supported if they operate on arrays for targets below ES6/ES2015.
TypeScript 2.3 adds full support for generators and the Iterator protocol for ES3 and ES5 targets with --downlevelIteration
flag.
With --downlevelIteration
, the compiler uses new type check and emit behavior that attempts to call a [Symbol.iterator]()
method on the iterated object if it is found, and creates a synthetic array iterator over the object if it is not.
Please note that this requires a native
Symbol.iterator
orSymbol.iterator
shim at runtime for any non-array values.
for..of
statements, Array Destructuring, and Spread elements in Array, Call, and New expressions support Symbol.iterator
in ES5/E3 if available when using --downlevelIteration
, but can be used on an Array even if it does not define Symbol.iterator
at run time or design time.
Async Iteration
TypeScript 2.3 adds support for the async iterators and generators as described by the current TC39 proposal.
Async iterators
The Async Iteration introduces an AsyncIterator
, which is similar to Iterator
.
The difference lies in the fact that the next
, return
, and throw
methods of an AsyncIterator
return a Promise
for the iteration result, rather than the result itself.
This allows the caller to enlist in an asynchronous notification for the time at which the AsyncIterator
has advanced to the point of yielding a value.
An AsyncIterator
has the following shape:
tsinterface AsyncIterator<T> { next(value?: any): Promise<IteratorResult<T>>; return?(value?: any): Promise<IteratorResult<T>>; throw?(e?: any): Promise<IteratorResult<T>>; }
An object that supports async iteration is said to be “iterable” if it has a Symbol.asyncIterator
method that returns an AsyncIterator
object.
Async Generators
The Async Iteration proposal introduces “Async Generators”, which are async functions that also can be used to yield partial computation results.
Async Generators can also delegate calls via yield*
to either an iterable or async iterable:
tsasync function* g() { yield 1; await sleep(100); yield* [2, 3]; yield* (async function *() { await sleep(100); yield 4; })(); }
As with Generators, Async Generators can only be function declarations, function expressions, or methods of classes or object literals.
Arrow functions cannot be Async Generators. Async Generators require a valid, global Promise
implementation (either native or an ES2015-compatible polyfill), in addition to a valid Symbol.asyncIterator
reference (either a native symbol or a shim).
The for-await-of
Statement
Finally, ES2015 introduced the for..of
statement as a means of iterating over an iterable.
Similarly, the Async Iteration proposal introduces the for..await..of
statement to iterate over an async iterable:
tsasync function f() { for await (const x of g()) { console.log(x); } }
The for..await..of
statement is only legal within an Async Function or Async Generator.
Caveats
- Keep in mind that our support for async iterators relies on support for
Symbol.asyncIterator
to exist at runtime. You may need to polyfillSymbol.asyncIterator
, which for simple purposes can be as simple as:(Symbol as any).asyncIterator = Symbol.asyncIterator || Symbol.for("Symbol.asyncIterator");
- You also need to include
esnext
in your--lib
option, to get theAsyncIterator
declaration if you do not already have it. - Finally, if your target is ES5 or ES3, you’ll also need to set the
--downlevelIterators
flag.
Generic parameter defaults
TypeScript 2.3 adds support for declaring defaults for generic type parameters.
Example
Consider a function that creates a new HTMLElement
, calling it with no arguments generates a Div
; you can optionally pass a list of children as well. Previously you would have to define it as:
tsdeclare function create(): Container<HTMLDivElement, HTMLDivElement[]>; declare function create<T extends HTMLElement>(element: T): Container<T, T[]>; declare function create<T extends HTMLElement, U extends HTMLElement>(element: T, children: U[]): Container<T, U[]>;
With generic parameter defaults we can reduce it to:
tsdeclare function create<T extends HTMLElement = HTMLDivElement, U = T[]>(element?: T, children?: U): Container<T, U>;
A generic parameter default follows the following rules:
- A type parameter is deemed optional if it has a default.
- Required type parameters must not follow optional type parameters.
- Default types for a type parameter must satisfy the constraint for the type parameter, if it exists.
- When specifying type arguments, you are only required to specify type arguments for the required type parameters. Unspecified type parameters will resolve to their default types.
- If a default type is specified and inference cannot choose a candidate, the default type is inferred.
- A class or interface declaration that merges with an existing class or interface declaration may introduce a default for an existing type parameter.
- A class or interface declaration that merges with an existing class or interface declaration may introduce a new type parameter as long as it specifies a default.
New --strict
master option
New checks added to TypeScript are often off by default to avoid breaking existing projects.
While avoiding breakage is a good thing, this strategy has the drawback of making it increasingly complex to choose the highest level of type safety, and doing so requires explicit opt-in action on every TypeScript release.
With the --strict
option it becomes possible to choose maximum type safety with the understanding that additional errors might be reported by newer versions of the compiler as improved type checking features are added.
The new --strict
compiler option represents the recommended setting of a number of type checking options. Specifically, specifying --strict
corresponds to specifying all of the following options (and may in the future include more options):
--strictNullChecks
--noImplicitAny
--noImplicitThis
--alwaysStrict
In exact terms, the --strict
option sets the default value for the compiler options listed above.
This means it is still possible to individually control the options.
For example,
sh--strict --noImplicitThis false
has the effect of turning on all strict options except the --noImplicitThis
option. Using this scheme it is possible to express configurations consisting of all strict options except some explicitly listed options.
In other words, it is now possible to default to the highest level of type safety but opt out of certain checks.
Starting with TypeScript 2.3, the default tsconfig.json
generated by tsc --init
includes a "strict": true
setting in the "compilerOptions"
section.
Thus, new projects started with tsc --init
will by default have the highest level of type safety enabled.
Enhanced --init
output
Along with setting --strict
on by default, tsc --init
has an enhanced output. Default tsconfig.json
files generated by tsc --init
now include a set of the common compiler options along with their descriptions commented out.
Just un-comment the configuration you like to set to get the desired behavior; we hope the new output simplifies the setting up new projects and keeps configuration files readable as projects grow.
Errors in .js files with --checkJs
By default the TypeScript compiler does not report any errors in .js files including using --allowJs
.
With TypeScript 2.3 type-checking errors can also be reported in .js
files with --checkJs
.
You can skip checking some files by adding // @ts-nocheck
comment to them; conversely you can choose to check only a few .js
files by adding // @ts-check
comment to them without setting --checkJs
.
You can also ignore errors on specific lines by adding // @ts-ignore
on the preceding line.
.js
files are still checked to ensure that they only include standard ECMAScript features; type annotations are only allowed in .ts
files and are flagged as errors in .js
files.
JSDoc comments can be used to add some type information to your JavaScript code, see JSDoc Support documentation for more details about the supported JSDoc constructs.
See Type checking JavaScript Files documentation for more details.