For what it's worth this is how I would propose to handle async
functions (the feature that has introduced type-directed emit to TypeScript).
-
Users just want to write
async function() {...}
and lettsc
do the magic. No messing with custom promises. -
No need for type-directed emit based on return type annotation.
-
Return type annotation can work like it does everywhere else (i.e. for type checking). If present it must be a type that's promise-like in the same way that a generator function return type annotation has to be iterator-like.
-
The user would have to provide a polyfill Promise implemtation in this scenario. Of course they can name this using any identifier they want but my suggestion would be that to work with TypeScript's
async
function feature, it must be calledPromise
(which is standard practice for a polyfill anyway). -
Promise
can be polyfilled as a global but shouldn't/needn't be if using a module system - it just has to be in scope (e.g.import Promise from 'bluebird'
at file/module scope using commonjs). -
The type system can check (at compile time) at the site of an
async function
declaration, if there is an in-scope identifier calledPromise
and that it has the right shape. If noPromise
identifier is in scope, generate a compile error like 'async function requires aPromise
identifier to be in scope'. -
Adopt
Promise
's type as the async function's return type. So if, say, thePromise
identifier is typed like a bluebird promise (which has more members than an ES6 promise), then that's the type returned by the async function. -
The downleveled emit uses the
Promise
identifier in the emitted code. -
Need to avoid the elision problem of #6003 (same as in current implementation).
-
No need for type-directed emit based on return type annotation.
-
Return type annotation can work like it does everywhere else (i.e. for type checking). If present it must be a type that's promise-like in the same way that a generator function return type annotation has to be iterator-like.
Scenario 3 (MAYBE COMMON?): target >=ES6, supports builtin Promise, but user wants to use a custom Promise implementation instead
-
e.g. platform is node 4.x/5.x but user wants to use 'bluebird' promises because they are faster.
-
This could be handled exactly the same as scenario 2.
Scenario 4 (PROBABLY UNCOMMON): target ESany, async functions must produce Promise instances of several different types in a single project, instead of or in addition to the builtin Promise
-
User wants to choose what Promise implementation is returned on a per-async-function basis.
-
This is the use case that tsc v1.7 is geared toward with its type-directed emit.
-
I personally haven't encountered a real world project with this need (scenarios 2 and 3 cover all practical 'custom promise' cases that I've seen). But I guess they may exist. Please speak up, anyone who knows of such a project.
-
Because async function declarations don't explicitly reference a
Promise
identifier, there's no obvious way to 'pass' a specific promise class to a specific async function declaration. -
Current implementation solves this scenario using the async function's return type annotation, but in so doing sacrifices TypeScript's goal of having a "fully erasable type system".
-
ES7 async function spec also needs to consider how custom promises could be supported. Notably any solution decided there would have to be a runtime mechanism, which TypeScript would presumably adopt in future. Or ES7 may not support this scenario at all. Either way the current type annotation method won't comply with ES7 syntax/semantics.
My personal view is that TypeScript should support scenarios 1 - 3 now and drop support for scenario 4 until the ES7 async function spec is finalised and the ES7 mechanism to support this scenario (if any) is known.
Scenarios 1 - 3 can be achieved in a straightforward way, fully consistent with TypeScript's "fully erasable type system", and without resorting to type-directed emit. That covers all practical cases that I have come across.
Scenario 4 is probably uncommon. TypeScript v1.7 is running ahead of the spec to support this and the final ES7 mechanism is 100% sure not to be a type annotation. It seems a shame to sacrifice the erasability of the type system to support an uncommon scenario in a way that won't match ES7 anyway.
As a breaking change I know backing out the type-directed emit is probably not gonna happen. But in case scenario 4 was dropped in v1.8, then the upgrade pathway from 1.7 to 1.8 would be from this:
import CustomPromise from 'custom-promise-lib';
export async function foo(): CustomPromise { }
...to this:
import Promise from 'custom-promise-lib';
export async function foo() { }
The latter case is already valid in v1.7 and anyone doing it that way already wouldn't see any breaking changes.