Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async Iteration and down-level Generators #12346

Merged
merged 51 commits into from
Feb 17, 2017
Merged

Async Iteration and down-level Generators #12346

merged 51 commits into from
Feb 17, 2017

Conversation

rbuckton
Copy link
Member

@rbuckton rbuckton commented Nov 18, 2016

This PR adds full support for Async Generators and Async Iteration (for..await..of) as per the current TC39 Proposal for all script targets. It also contains full support for Generators in ES5/3, including for..of, spread, and iterator destructuring.

Iterators

ES6 introduced Iterator, which is an object that exposes three methods, next, return, and throw, as per the following interface:

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

Async Iterators

The Async Iteration proposal 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:

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

Generators

ES6 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:

function* f() {
  yield 1;
  yield* [2, 3];
}

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:

async function* f(p) {
  yield 1;
  await p;
  yield* [2, 3];
  yield* (async function *() { 
    await p; 
    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 ES6-compatible polyfill), in addition to a valid Symbol.asyncIterator reference (either a native symbol or a shim).

The for-await-of Statement

Finally, ES6 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:

async function* g() { ... }
async function f() {
  for await (const x of g()) {
  }
}

The for..await..of statement is only legal within an Async Function or Async Generator.

Generators and Iteration for ES5/3

In addition, the following changes are included to bring full support for Generators to ES5/ES3:

  • Added a -downlevelIteration compiler option, used when targeting ES5 or ES3.
    • When set, 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. This requires a native Symbol.iterator or Symbol.iterator shim at runtime for any non-array values.
    • When not set, array destructuring, spread, and for..of are only supported for arrays as is the current behavior in TypeScript 2.1 and earlier.
  • Array Destructuring now uses Symbol.iterator in ES5/3 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.
  • Spread elements in Array, Call, and New expressions use Symbol.iterator in ES5/3 if available when using -downlevelIteration, but can be used on Array even if it does not define Symbol.iterator at run time or design time.
  • for..of statements support Symbol.iterator in ES5/3 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 Functions do not require a Symbol.iterator polyfill at run time to operate.

Related issues

@TonyPythoneer
Copy link

TonyPythoneer commented Feb 10, 2017

This PR is planed on TypeScript 2.3 release. 😭
I hope this can be released soon.

@rbuckton
Copy link
Member Author

@mhegazy, Is there anyone else you want to review this or should I merge?

];

var es2017LibrarySourceMap = es2017LibrarySource.map(function (source) {
return { target: "lib." + source, sources: ["header.d.ts", source] };
});

var esnextLibrarySource = [
"esnext.asynciterable.d.ts"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to update VS setup to include this file as well.

@@ -272,7 +272,7 @@ namespace ts {

function forEachLeadingCommentToEmit(pos: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) {
// Emit the leading comments only if the container's pos doesn't match because the container should take care of emitting these comments
if (containerPos === -1 || pos !== containerPos) {
if ((containerPos === -1 || pos !== containerPos)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the parens are not needed

@@ -284,7 +284,7 @@ namespace ts {

function forEachTrailingCommentToEmit(end: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean) => void) {
// Emit the trailing comments only if the container's end doesn't match because the container should take care of emitting these comments
if (containerEnd === -1 || (end !== containerEnd && end !== declarationListContainerEnd)) {
if ((containerEnd === -1 || (end !== containerEnd && end !== declarationListContainerEnd))) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

@@ -21132,7 +21287,7 @@ namespace ts {
function checkExternalEmitHelpers(location: Node, helpers: ExternalEmitHelpers) {
if ((requestedExternalEmitHelpers & helpers) !== helpers && compilerOptions.importHelpers) {
const sourceFile = getSourceFileOfNode(location);
if (isEffectiveExternalModule(sourceFile, compilerOptions)) {
if (!isDeclarationFile(sourceFile) && isEffectiveExternalModule(sourceFile, compilerOptions)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you need this here, should not the new helpers only be visible in expressions?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it is needed, then it should be isInAmbientContext

@jocull
Copy link

jocull commented May 30, 2017

Just curious, why is the downlevelIteration still undocumented under tsc --help? It seems to work, but there's no mention of it, and the errors leading up to needing it are unhelpful.

@mhegazy
Copy link
Contributor

mhegazy commented May 30, 2017

Just curious, why is the downlevelIteration still undocumented under tsc --help?

it is, just use tsc --help --all

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.