
TypeScript 3.7 - nerdkid93
http://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html
======
achou
I just did some refactoring on a medium size code base and here are a few
things to watch out for when adopting optional chaining and the new null
coalescing operator:

    
    
      foo && await foo();
    

is not the same as

    
    
      await foo?.();
    

this will work in most cases but subtly, the await wraps the undefined case
into a Promise, while the original code would skip the await altogether.

String regular expression matching returns null, not undefined, so rewriting
code such as:

    
    
      const match = str.match(/reg(ex)/);
      return match && match[1];
    

is not the same thing as:

    
    
      return match?.[1];
    

because the latter returns undefined, not null, in case of match failure. This
can cause problems if subsequent code expects null for match failure. An
equivalent rewrite would be:

    
    
      return match?.[1] ?? null;
    

which is longer than the original and arguably less clear.

A common idiom to catch and ignore exceptions can interact poorly with
optional chaining:

    
    
      const v = await foo().catch(_ => {});
      return v?.field; // property 'field' does not exist on type 'void'
    

This can be easily remedied by changing the first line to:

    
    
      const v = await foo().catch(_ => undefined);
    

Of course, these new operators are very welcome and will greatly simplify and
help increase the safety of much existing code. But as in all things syntax,
being judicious about usage of these operators is important to maximize
clarity.

~~~
paulddraper
&& ?. || ??

It's a shame JS at the beginning doubled down on the "billon dollar mistake"
[1] with two(!) kinds of NULL instead of just using Maybe/Option.

Ah well, if it were good it wouldn't be popular :/

[1] [https://www.lucidchart.com/techblog/2015/08/31/the-worst-
mis...](https://www.lucidchart.com/techblog/2015/08/31/the-worst-mistake-of-
computer-science/)

~~~
vikingcaffiene
Sorry, JS? While JS might get this stuff one day, these language features are
for _TypeScript_ which is its own language. It's strongly typed and just
happens to interop with and in some scenarios transpile down to JavaScript.
It's whole existence is to deal with that billion dollar mistake you
mentioned.

Speaking of which, optional chaining and null coalescence are core language
features of some very good languages. Kotlin and C# for instance. Kotlin, much
like TypeScript, interops with a "broken" language (Java and the JVM in its
case) and attempts to address some core deficiencies in that ecosystem. Let us
have nice things!! :)

I am really excited about these new features and hope they do land in JS
sooner rather than later. I hope they do the pipe operator next! `pipe |>
operator|> plz`

~~~
autora
> just happens to interop with

It does not interop with JavaScript.

> and in some scenarios transpile down to JavaScript

It _always_ transpiles to JavaScript and _always_ runs as JavaScript. There is
no such thing as a TypeScript runtime engine.

TypeScript is a superset of JavaScript. Therefore the OP's point is still
valid. Any "mistakes" JavaScript might have made about having null AND
undefined are also issues for TypeScript.

~~~
Zarel
> It does not interop with JavaScript.

I don't know what you mean here, but it is certainly possible for TypeScript
code to use JavaScript libraries and vice versa, which is presumably what most
people mean by "TypeScript interops with JavaScript".

> It always transpiles to JavaScript and always runs as JavaScript.

Technically false... [https://assemblyscript.org](https://assemblyscript.org)

> TypeScript is a superset of JavaScript. Therefore the OP's point is still
> valid. Any "mistakes" JavaScript might have made about having null AND
> undefined are also issues for TypeScript.

TypeScript adds type checking to JavaScript. The Million Dollar Mistake is
having unchecked nulls; TypeScript supports checked nulls so it's not an
issue. TypeScript's nulls are much more similar to Maybe/Option than unchecked
nulls.

~~~
autora
> I don't know what you mean here, but it is certainly possible for TypeScript
> code to use JavaScript libraries and vice versa, which is presumably what
> most people mean by "TypeScript interops with JavaScript".

Ah, I can see what you/they mean by that. The point I was trying to get across
was: TypeScript doesn't exist when code is actually executing (which is what I
think of as interop - it's happening at execution time.) At execution time -
it's all just JavaScript.

I have found (working in a TypeScript team currently) that this fact is
ignored, primarily by people who "look down" on JavaScript, but it is a VERY
important point to remember when you are writing TypeScript, mostly because
it's important to remember there is only compile time type checking not run
time.

> Technically false...
> [https://assemblyscript.org](https://assemblyscript.org)

Heh, yes - as soon as I posted I realised that was silly. The word "always" is
almost "always" incorrect! I should have said: "It usually transpiles to
JavaScript and usually runs as JavaScript"

> The Million Dollar Mistake is having unchecked nulls; TypeScript supports
> checked nulls so it's not an issue. TypeScript's nulls are much more similar
> to Maybe/Option than unchecked nulls

Good point in theory but my practical experience hasn't borne this out. That
is because TypeScript is an "optionally typed" language and it hasn't been
true in practice because of excessive use of explicit or implicit "any"s.

~~~
squiggleblaz
<i> > The Million Dollar Mistake is having unchecked nulls; TypeScript
supports checked nulls so it's not an issue. TypeScript's nulls are much more
similar to Maybe/Option than unchecked nulls</i>

<i>Good point in theory but my practical experience hasn't borne this out.
That is because TypeScript is an "optionally typed" language and it hasn't
been true in practice because of excessive use of explicit or implicit "any"s.
</i>

I think that's a matter of your team's discipline. It's good practice, I
think, to enable TypeScript's strict checks, including no-implicit-any, and,
to the best of your ability, to keep people who don't understand types
ignorant of explicit any and to fail any code that uses it. `any` is basically
never necessary even in typing existing code - if you genuinely don't know
what the type is at a certain point, you should probably write a type like
`unknown`.

If you take any of Typescript's options to "ease the transition" you're taking
Typescript's options to continue the difficulties. One moves to typescript
because javascript's runtime errors are a problem; so it is natural that you
will have novel compile time errors.

------
reggieband
I'm a big fan of the new operators.

One of my remaining gripes with Javascript/Typescript is the try/catch mess
around await. It makes assignment of const a pain for async calls that may
reject.

e.g.

    
    
        let result: SomeType;
        try {
            result = await funcThatReturnSomeType();
        } catch (err) {
            doSomethingWithErr(err);
        }
    
        // at this point result is `SomeType | undefined`
        
        if (result) {
            doSomething(result);
        }
    

I really want some kind of structures that allow me to make `result` constant.
In some cases I've rolled my own Maybe/Either wrapper and then move the
try/await/catch into a function but that is still a pain.

This is such a common pattern in my code ... I wish there was a more elegant
way to deal with it.

~~~
yawaramin
IIFEs are an option:

    
    
        const result = await (() => {
          try {
            return funcThatReturnSomeType();
          } catch (err) {
            doSomethingWithErr(err);
          }
        })();

~~~
saurik
I don't think this is correct, as the try catch will only catch errors that
happen while returning the promise, not awaiting it; you need to do this:

    
    
        const result = await (async () => {
          try {
            return await funcThatReturnSomeType();
          } catch (err) {
            doSomethingWithErr(err);
          }
        })();

~~~
yawaramin
You may be right, proving the old adage that the best way to get an answer on
the internet is to post a wrong answer ;-)

------
nikeee
I really like the optional chaining operator for statically typed languages.
Especially in TypeScript where you have the nullabilaty information baked into
the type system.

However, in JS itself, it might cause developers to lose track of what can be
null/undefined in their code. In case they start to "fix" stuff by throwing in
some "?." because they don't know better, the code maintainability will
degrade a lot.

Maybe I'm just pessimistic. Let's see how it will perform in the field!

~~~
nicoburns
It's nice for situations where you want to access a deeply nested prop, and
you only care whether the whole path is there or not. Saves you having to add
a seperate check for every level of the hierarchy.

e.g. You can do:

    
    
        foo?.bar?.baz || "default";
    

Rather than:

    
    
        (foo && foo.bar && foo.bar.baz) || "default";
    

Agree that developers can be careful about nullability (in fact I pulled
someone up on this in a code review earlier today), but I don't think this
feature makes that any worse.

~~~
erikpukinskis
The problem is that if you find yourself needing deep accessors, something is
very wrong with your scopes. You are reaching across many levels of concerns
which is a code smell.

So, by making it “nice” you are making a code smell less smelly, which feels
good in the moment, at the syntax level, but makes your code worse at the
architecture level.

This is roughly the story for all of ES6... make it “nice” to work with bad
code, allowing bad code to look more similar to good code, until everything
looks “nice” at the syntax level but you are surrounded by footguns that are
impossible to find, and you need more and more static analysis tools (like
TypeScript) to even be able to comprehend your control structures.

Callback hell isn’t bad because of indentation, it’s bad because there are too
many handoffs in a small space. Promises make it easier to pack more handoffs
into a small space, and guess what? Now the problem is even worse.

This new ? operator will make it easier than ever to pass on undefined values.
In other words, it will make the problem it solves even worse.

~~~
LocalPCGuy
> if you find yourself needing deep accessors, something is very wrong with
> your scopes

I'm not sure I agree with that statement in all scenarios. For code you
control, sure.

But there are many APIs that return very deeply nested structures that are
inconsistent in their shape. That, in my view, is the most common place devs
will need deep accessors where parts may be null/undefined somewhere in
between the root object and the key they are trying to access.

Sure, they could write functions that, similar to get in lodash, expose just
the values needed, at which point chaining wouldn't be needed at all in the
code that deals with that value. Or it could be serialized into a class
object, but again, the chain would be dealt with in the serialization. At some
point, the chain needs to be dealt with and often the JSON structures from
APIs are not something that's always under our control.

------
wayneftw
Optional Chaining also coming soon to create-react-app, planned for v3.3
(current release is react-scripts@3.2.0) -
[https://github.com/facebook/create-react-
app/pull/7438](https://github.com/facebook/create-react-app/pull/7438)

I think they're just waiting on support for the new syntax in prettier.

~~~
ljm
Is it not bizarre that you're blocked on a syntax formatter to make a release?
If that's what's happening, it sounds like prettier should be core and non-
optional, the same way gofmt is.

~~~
eduren
The commenter was talking about a release of create-react-app, which is a
starter-kit/scaffold. For such a project with the goals of being a packaged
collection of tools/configurations, it seems prudent to only release if your
set of tools present a consistent experience.

Many js developers see formatters as a sane default, and create-react-app is a
layer in the ecosystem that incorporates it as a "core" piece in the way that
you meant above.

------
dashwav
I really like the new optional operator, this might be what gets me to bite
the bullet and start moving some of my projects over to typescript - dealing
with potential undefined objects in those chains is one of the things I
actively dislike about writing in vanilla Javascript.

~~~
andrewingram
I'm assuming by optional operator you're referring to optional chaining? If
so, it's very cool but strikes me as an odd reason to move to TypeScript,
because it's a stage 3 proposal in JavaScript too, so is likely to be widely
supported soon.

~~~
nikeee
The TypeScript team is only implementing features that have a chance of 100%
of landing in JS or 0%. Therefore, they wait for Stage 3.

If they start implementing features at an earlier stage, there is the risk of
implementing a feature with different semantics in TS than in JS, since the JS
spec can still change (or event get rejected). Both cases will result in
diverging languages, which is something they try to avoid.

Edit: In the past, that didn't work that well. TS 3.8 is planned to ship with
JS private fields support (the one with the # syntax). TS had private fields
for a long time. In fact, it's one of the first things that the language had.

However, these private fields behave entirety different to what ended up in
the ES private fields spec. They can't change it afterwards. So in the future,
we will have two semantically different ways of declaring a private field in a
class in TS.

Another case are decorators. They are still in stage 2 and may change. They
already exist in TS, behind a flag. But every Angular application depends on
their current implementation in TS. If the spec changes, it will get
interesting.

~~~
LocalPCGuy
> They can't change it afterwards.

I would argue that TypeScript absolutely can change things that would be
consider breaking changes. And they do have breaking changes in just about
every release (sure wish they followed semver for that reason). They'd just
want to be careful for big things like private fields or decorators, making
sure the breaking changes get communicated publicly and loudly.

------
findjashua
now if they can just add a compiler flag for "immutable by default":

[https://github.com/microsoft/TypeScript/issues/32758](https://github.com/microsoft/TypeScript/issues/32758)

------
SirensOfTitan
I feel really excited about 3.7. Optional chaining and null coalescing will
clean up a TON of code.

... but with that being said, 3.7 seems to have broken many aspects of the
`Promise.all` interface. Right now the largest issue seems to be that if any
`Promise` result in `Promise.all` is nullable, all of the results are
nullable.

~~~
nine_k
Indeed, how can you declare a list with _some_ elements nullable, and some
not?

The result should instead be a tuple, but IDK how well tuple size inference
would work in a case like that.

~~~
52-6F-62
I’m not at a computer now, but you can explicitly define a tuple type like:

    
    
        type Tuple<T, K> = [T, K | null];
    

Which is my first thought, but I can’t test it against the compiler at the
moment and I’m not sure if I’m missing something.

...JavaScript would allow you to extend that list during runtime (unless you
freeze it)

    
    
        type Tuple<T, K> = [T, K]
        const Tuple<T, K> = (x: T, y: K): Tuple<T, K> => {
            const tup = [x, y];
            Object.freeze(tup);
            return tup;
        };

------
munificent
The optional chaining and null coalescing operators are very nice. Dart has
had those for several years and they really do come in handy.

~~~
grapehut
Definitely. It's a shame that dart gets so much unwarranted hate. I mean, I
also think it's an absolutely awful language but it's really proven to be a
valuable source of data for what other programming languages should do and
perhaps more importantly: not do. I really hope we see a lot more things like
Dart, and not so much negativity.

~~~
munificent
Depending on when you last looked at Dart, there's a good chance we've either
fixed or are fixing the things you hate about it. What didn't you like?

~~~
virtualwhys
> What didn't you like?

That the suggested features in this open issue [1] can't be implemented soon
enough :)

Is there a roadmap available that would give an idea as to when x, y, z
language features may be implemented?

[1] [https://github.com/dart-
lang/language/issues/546](https://github.com/dart-lang/language/issues/546)

~~~
munificent
We don't tend to have detailed roadmaps because it risks setting people up for
disappointment when schedules change. But what's roughly happening right now
is:

\- Extension members are basically done and out the door. (See:
[https://medium.com/dartlang/extension-
methods-2d466cd8b308](https://medium.com/dartlang/extension-
methods-2d466cd8b308))

\- Non-nullable types are well underway. All but a few corners of the design
are pinned down, much of the static checking is implemented, the core
libraries have been mostly migrated, and we're working through the runtime
implementation, migration tests, etc.

\- Next up after that, the current plan (which may change) is control over
variance and stuff around pattern matching.

We're working on it.

------
mceachen
The preview release announcement for this version made FP on HN already, but
it's a biggie:

* Optional Chaining & Coalescing

* Assertion Functions

* .d.ts Emit From .js Files

* Smarter Control Flow Analysis

* Flatter Error Messages

also great:

* Function Truthy Checks / Uncalled Function Checks (which was tslint's biggest value prop for me up until now)

------
vosper
What's the end state for TypeScript? Does it one day become feature complete
and slow down? One of the frustrating things about trying to find help with TS
today as a beginner is the plethora of SO and blog posts talking about much
older versions. If you're lucky there'll be some comment "As of 2.6 you can
now do ...", but even 2.6 is a lot of versions back - how do I know that's
still the best way to do it in 3.7?

I'm for progress, but it makes me a little wary to start my team of no-
previous-TS-experience JS devs on a TypeScript project when there's still a
new version every few months. Keeping up with Webpack is enough of a hassle...

~~~
wwwigham
JavaScript itself keeps changing, so we need to keep pace with that, for one.
Beyond that, we relentlessly seek to improve how people interact with our
editor tools, and make additions to the (type) language and compiler to
support that. .d.ts files from js files, for example, are highly motivated by
a desire to better support incremental compilations with .js inputs, as .d.ts
files are used as incremental metadata. Assertion signatures were added to
better express the cross-call control flow patterns some (assertion) libraries
already use, to make using them in a well-typed way more ergonomic.

Generally speaking, it's not often we add something that _invalidates_ the old
way to do something (in the language) - our additions are usually made to make
new patterns possible to express.

~~~
vosper
Thanks for the response. I do appreciate the efforts of the team, and am
planning to continue learning TypeScript :)

Maybe this exists, but one thing that would be super helpful is a document
that explains any places where there's a "new best way" of doing things, and
also details what the old way was.

Here's an example from yesterday: I was Googling about extending a type (I
think) and the first SO result (top of Google) says "you can't do that in
TypeScript"... but reading the comments someone had said "actually you can, in
2.6 or later". That's the kind of thing that it would be great to have
summarised in one place. Not necessarily all the changes (ie, not just all the
release notes) but specifically where the language has changed and an old way
of doing things, or a previous restriction, is gone. Preferably with examples.
(I'd try to create this, but I don't have the skills to do so).

~~~
wwwigham
We actually primarily use StackOverflow for this (as it's the first resource
many devs go to, as you did, and it's collaborative). We even pre-seed
questions and answers for releases, sometimes.

If you find an answer on SO is out of date - suggest a new answer (or ask for
one) and get it updated (there's far, far too many for us to keep explicit
track of them, there's only a handful of us on the team)! :D

------
alipang
Great incremental release.

Typescript really isn't the most exciting language, but it's very very
helpful. Compared to regular javascript it saves me a lot of time, and so many
pains and headaches every day.

~~~
iLemming
Ha. Try Clojurescript. You'll be surprised.

~~~
huy-nguyen
Or ReasonML.

~~~
Rapzid
Or F#.

------
koolba
Does the function argument (i.e. the template string) get evaluated regardless
of the optional chaining or does it match up with the “roughly equivalent”
code?

    
    
        log?.(`Request started at ${new Date().toISOString()}`);
        // roughly equivalent to
        //   if (log != null) {
        //       log(`Request started at ${new Date().toISOString()}`);
        //   }

~~~
sicromoft
Why would it not match up with the example given? They provided it so you'd
know the answer to your question.

~~~
koolba
I copied the example from the article but it says “roughly” so it’s not
entirely clear.

The situation that popped in my mind was something like: f?(++a)

Normally you’d expect the side effects incrementation to occur prior to the
start of the function invocation. If the function is not evaluated it’s not
clear from the article if increment will occur.

------
dstaley
I'm super excited about this release, but it got off to a rocky start for me.
Prior to 3.7, all our tests worked fine, but something in 3.7 changed that
caused the type-checker to fail previously valid code. What's worse is that I
can't reproduce the issue in a playground. Thankfully the workaround is "make
your code more explicit", which is fine, but it was just a surprise to see
something like this break.

For those that are curious, here's the error that 3.7 introduced:

    
    
      Type 'SinonStub<[string, RequestBody, (RequestOptions | undefined)?], KintoRequest>' is not assignable to type 'SinonStub<any[], any>'.
      Type 'any[]' is missing the following properties from type '[string, RequestBody, (RequestOptions | undefined)?]': 0, 1
    

I think the issue here is that `[string, RequestBody, (RequestOptions |
undefined)?]` is a tuple type, and `any[]` is an array type. That being said
though, I'd expect that a tuple would satisfy a `any[]` type.

~~~
rraval
> That being said though, I'd expect that a tuple would satisfy a `any[]`
> type.

You have it backwards, the error in question is complaining that `any[]` does
not satisfy the tuple type.

Minimal repro:

    
    
        function f(x: any[]): [number, string] {
            return x;
        }
    

The error matches yours:

    
    
        Type 'any[]' is missing the following properties from type '[number, string]': 0, 1
    

From poking the playground, `any[]` hasn't been assignable to tuples since at
least v3.3.3

My guess is that the compiler got smarter around reasoning about your
`SinonSub` generic and is now forcing you to deal with lingering unsoundness.

~~~
dstaley
Yeah, that's my assumption as well. It would explain why there's no mention of
it in breaking changes, and why being specific about the generics in
`SinonStub` fixes the error.

Makes me wonder what other unsoundness the compiler isn't catching.

------
macca321
Now I know how people felt when they told me C# was adding features so quickly
they couldn't keep up.

------
orta
There's a bunch of website work that came out with this release too: mainly
search and a lot of playground improvements.

[https://www.typescriptlang.org](https://www.typescriptlang.org)

------
seanwilson
For this code:

    
    
        const x = [1,2];
        const y = x[666];
        const z = y + 3;
    

Is there a way for TypeScript to flag the last line as a type error?

TypeScript will say "y" has type "number" when "x[666]" returns undefined. Why
does TypeScript not say the type of "y" is "number | undefined"?

~~~
scottlecrab
[https://www.typescriptlang.org/play/index.html#code/MYewdgzg...](https://www.typescriptlang.org/play/index.html#code/MYewdgzgLgBAHjAvDA2gRgDQCYC6MCGEMokUA3AFAnQwCeS8KAbCzpdbAF4P0DUMAZjJA)

Using `as const` will report both the 2nd and 3rd lines as type errors. You've
been able to do this in TypeScript for a while even before they introduced the
`as const` syntax.

~~~
seanwilson
Thanks, you can still trick it with this though (there's no type error):

    
    
        const x = [1, 2] as const;
        const r = 666 + 1;
        const y = x[r];
        const z = y + 3;

~~~
nine_k
Let's wait until TS supports dependent types!

------
XCSme
Very cool features for writing shorter code! I also noticed a small mistake in
their examples: [https://github.com/microsoft/TypeScript-
Handbook/issues/1135](https://github.com/microsoft/TypeScript-
Handbook/issues/1135)

~~~
shhsshs
Good catch!

------
russley
This is going to be one of my favorite releases since 2.8, which added
conditional types. So many bad utility functions will be able to go away. The
only thing it seems to be missing is variadic type generics.

------
Diesel555
Optionals! I wrote Swift before typescript, and I'm a huge fan of these new
operators.

------
knocte
From the snippets in the release notes:

```

function dispatch(x: string | number): SomeType {

    
    
        if (typeof x === "string") {
    
            return doThingWithString(x);
    
        }
    
        else if (typeof x === "number") {
    
            return doThingWithNumber(x);
    
        }
    
        process.exit(1);
    

}

```

It's very embarrassing in my opinion that they haven't done anything yet
against having these horrible kind of type checking; comparing against a
string that has the type name? "string", "number"? it's completely ludicrous.

I will not take this language seriously until this is fixed. (For sure it's
still better than JavaScript, but that's about it.)

~~~
MaulingMonkey
You can define your own type guards:

    
    
        function isString(x: any): x is string {
            return typeof x === "string";
        }
        
        function isNumber(x: any): x is number {
            return typeof x === "number";
        }
        
        function dispatch(x: string | number): SomeType {
            if (isString(x)) {
                doThingWithString(x);
            } else if (isNumber(x)) {
                doThingWithNumber(x);
            }
            process.exit(1);
        }
    

TypeScript seeks to manage JavaScript's horrors, but importantly, it does
_not_ seek to hide them behind leaky abstractions. This is not an
embarassment, but TypeScript's strength and weakness.

There is a long list of other languages that compile down to JavaScript or
WASM, if you want a language built from clean foundations. But if you want to
add gradual typing as a means of slowly reigning in your existing JavaScript
behemoth? There is only one TypeScript.

