Skip to content

Instantly share code, notes, and snippets.

@yortus
Created December 13, 2015 05:05
Show Gist options
  • Save yortus/9df1ca6078639eb63eff to your computer and use it in GitHub Desktop.
Save yortus/9df1ca6078639eb63eff to your computer and use it in GitHub Desktop.
[OUTDATED] Proposed handling of TS async functions to avoid type-directed emit

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).

Scenario 1 (COMMON): target >= ES6, normal usage, no custom Promise needed

  • Users just want to write async function() {...} and let tsc 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.

Scenario 2 (COMMON): target ES3/ES5, no built-in Promise

  • 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 called Promise (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 called Promise and that it has the right shape. If no Promise identifier is in scope, generate a compile error like 'async function requires a Promise identifier to be in scope'.

  • Adopt Promise's type as the async function's return type. So if, say, the Promise 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment