question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Suggestion for improving generator and async function type checking

See original GitHub issue

This is a suggestion to improve both the consistency and the type-safety of return type checking for generator functions (GFs) and async functions (AFs).

NB: this issue refers to tsc behaviour for target >=ES6 with typescript@next as of 2016-01-28 (ie since commit https://github.com/Microsoft/TypeScript/commit/a6af98e10025492e0afda315ea37cfebe0b2bbfb)

Current Behaviour

Consider the following examples of current behaviour for checking return type annotations:

// Current behaviour - generator functions
class MyIterator implements Iterator<any> {next}
function* gf1(): any {}             // OK
function* gf2(): MyIterator {}      // OK (but really is an error)

// Current behaviour - async functions
class MyPromise extends Promise<any> {}
async function af1(): any {}        // ERROR (but is really not an error)
async function af2(): MyPromise {}  // ERROR

Problems with Current Behaviour

Firstly, type checking is not consistent across the two kinds of functions. In the examples the GF checking is too loose and the AF checking is too strict. The inconsistency is due to the different approach to checking the return type. The two approaches may be summarised like this:

  • Generator functions: accept any return type annotation that is assignable from IterableIterator<T>.
  • Async functions: reject all return type annotations other than references to the global Promise<T>. This is a recent change, the rationale for it can be followed from here.

Secondly, the type checker only gets 2 out of 4 of the above checks right (gf1 and af2). Explanation:

  • GOOD: gf1’s return type annotation is not super helpful but is 100% consistent with the type system. No sense erroring here, so the implementation is good.
  • BAD: gf2’s return type annotation passes type checking because it passes the assignability check. However gf2 definitely does not return an instance of MyIterator. All generator functions return a generator object, so at runtime gf2() instanceof MyIterator is false. A compile error would have been helpful.
  • BAD: af1’s return type annotation is just like gf1: not super helpful but 100% consistent with the type system. The compiler errors here even though nothing is wrong (reason for the error is here).
  • GOOD: af2’s return type annotation fails type checking because it’s not Promise<T>. The return type definitely won’t be an instance of any class other than Promise, so the implementation is good.

Suggested Improvement

Since GFs and AFs always return instances of known instrinsic types, we can rule out any type annotation that asserts they will return an instance of some other class.

Both generator and async functions could therefore be checked with the same two steps:

  1. Is Assignable: Ensure the return type of the GF/AF is assignable to the annotated return type. This is the basic check for all kinds of function return types. If not assignable, type checking fails. Otherwise, continue to step 2.
  2. Not a Class Type: Ensure the return type annotation is not a class type (except Promise<T> which is allowed for AFs). For example if the return type annotation is Foo, ensure it does not refer to class Foo {...} or another class-like value.

These rules have the following effects:

  • GF and AF type checking are mutually consistent.
  • This fixes gf2 by ruling out class types like MyIterator in addition to checking assignability. GF type checking is made safer in general by catching a class of errors that currently slip through.
  • This fixes af1, because it’s no longer necessary to rule out all annotations other than Promise<T>, but just those that are assignment-compatible class types like MyPromise. This approach will catch the breaking change from 1.7 as a compile error (as desired for reason here), but allow harmless (and correct) things like any and PromiseLike<T>.

Working Implementation

This is a small code change. I implemented this in a branch as best I could (but I may have made errors since I’m still getting my head around the codebase). The diff can be seen here.

With this version of tsc the above code works as follows:

// Suggested behaviour - generator functions
class MyIterator implements Iterator<any> {next}
function* gf1(): any {}             // OK
function* gf2(): MyIterator {}      // ERROR: A generator cannot have a return type annotation of a class type.

// Suggested behaviour - async functions
class MyPromise extends Promise<any> {}
async function af1(): any {}        // OK
async function af2(): MyPromise {}  // ERROR: An async function cannot have a return type annotation of a class type other than Promise<T>.

Issue Analytics

  • State:open
  • Created 8 years ago
  • Reactions:2
  • Comments:14 (11 by maintainers)

github_iconTop GitHub Comments

1reaction
yortuscommented, Jul 19, 2018

@nwalters512 for ES6+ targets, async functions will never return anything other than the built-in Promise type. You can check it by debugging your own sample code above. If you set a breakpoint on the return promise; line and inspect the value, it has the childProcess property as you describe. However if you add code to call this async function and then inspect the result type, it is a standard Promise and does not have the childProcess property.

This is the expected behaviour of async functions. They work fine with promise-like values in their body, but their return value is always a built-in Promise.

0reactions
yortuscommented, Jul 19, 2018

The return value will not be a Promise<ChildProcessPromise> despite passing the type checker. A promise never resolves to another promise, by design. The Promise instance returned by a call to ls will adopt the state of the special promise instance that was created inside the function. So it will resolve or reject when that inner promise resolves or rejects. But anything else special about that inner promise is lost.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Using Async/Await with Generator Functions | Pluralsight
Generator functions give you the ability to pause and continue the execution of a program. In contrast, async/await gives you the ability to ......
Read more >
Building A Server-Side Application With Async Functions and ...
First, I'll recap what async functions are and how they work. ... the generator solution requires that we change the function type to...
Read more >
JavaScript generators: The superior async/await
What makes JavaScript generator functions so different is that they do not initially execute, and instead they return an iterator object with a ......
Read more >
Exploring Async/Await Functions in JavaScript - DigitalOcean
The functionality achieved using async functions can be recreated by combining promises with generators, but async functions give us what we ...
Read more >
Typing generator functions in Redux-Saga with Typescript
However we can get return type of generator function to whom we delegated control to. So instead of yielding a call effect we...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found