Async Iterators in JS and TS

https://github.com/tc39/proposal-async-iteration

In Chrome, this is supported

In Typescript <2.3 , a function may be a generator or an async function, but it cannot be both. The asynchronous iteration proposal removes this restriction.

This works in TS 2.3: https://github.com/Microsoft/TypeScript/issues/14151

Here’s an example from their readme:

async function* readLines(path) {
  let file = await fileOpen(path);

  try {
    while (!file.EOF) {
      yield await file.readLine();
    }
  } finally {
    await file.close();
  }
}

Types

Generator functions declare several interfaces:

interface IteratorResult<T> {
    done: boolean;
    value: T;
}
interface Iterator<T> {
    next(value?: any): IteratorResult<T>;
    return?(value?: any): IteratorResult<T>;
    throw?(e?: any): IteratorResult<T>;
}
interface Iterable<T> {
    [Symbol.iterator](): Iterator<T>;
}
interface IterableIterator<T> extends Iterator<T> {
    [Symbol.iterator](): IterableIterator<T>;
}

Async generators return promises instead of their IteratorResult, and use a new symbol, Symbol.asyncIterator:

interface IteratorResult<T> {
    done: boolean;
    value: T;
}
interface AsyncIterator<T> {
    next(value?: any): Promise<IteratorResult<T>>;
    return?(value?: any): Promise<IteratorResult<T>>;
    throw?(e?: any): Promise<IteratorResult<T>>;
}
interface AsyncIterable<T> {
    [Symbol.asyncIterator](): AsyncIterator<T>;
}
interface AsyncIterableIterator<T> extends AsyncIterator<T> {
    [Symbol.asyncIterator](): AsyncIterableIterator<T>;
}

Emit

I guess that this would require another helper function to downlevel to ES2015, similar to the one used for async functions. Yield and await expression would both be replaced by yield expression, so they need to be decorated with some tag, like yield ['await', promise] for await promise.

for-await-of

The proposal also introduces a new loop. An example from their readme:

for await (const line of readLines(filePath)) {
  console.log(line);
}

This iterates over the [Symbol.asyncIterator]() object and awaits the value of .next(), similar to a for-of loop. This loop can only be used in async functions.

 

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols

Iterator examples

Simple iterator

function makeIterator(array) {
    var nextIndex = 0;
    
    return {
       next: function() {
           return nextIndex < array.length ?
               {value: array[nextIndex++], done: false} :
               {done: true};
       }
    };
}

var it = makeIterator(['yo', 'ya']);

console.log(it.next().value); // 'yo'
console.log(it.next().value); // 'ya'
console.log(it.next().done);  // true

A generator object is both, iterator and iterable:

var aGeneratorObject = function* () {
    yield 1;
    yield 2;
    yield 3;
}();
typeof aGeneratorObject.next;
// "function", because it has a next method, so it's an iterator
typeof aGeneratorObject[Symbol.iterator];
// "function", because it has an @@iterator method, so it's an iterable
aGeneratorObject[Symbol.iterator]() === aGeneratorObject;
// true, because its @@iterator method return its self (an iterator), so it's an well-formed iterable
[...aGeneratorObject];
// [1, 2, 3]

The spread syntax allows an expression to be expanded in places where multiple arguments (for function calls) or multiple elements (for array literals) or multiple variables  (for destructuring assignment) are expected.

Syntax

For function calls:

myFunction(...iterableObj);

For array literals:

[...iterableObj, 4, 5, 6];

Examples

A better apply

Example: it is common to use Function.prototype.apply in cases where you want to use an array as arguments to a function.

function myFunction(x, y, z) { }
var args = [0, 1, 2];
myFunction.apply(null, args);

With ES2015 spread you can now write the above as:

function myFunction(x, y, z) { }
var args = [0, 1, 2];
myFunction(...args);

Only for iterables

Note that the spread operator can be applied only to iterable objects:

var obj = {'key1': 'value1'};
var array = [...obj]; // TypeError: obj is not iterable

Assignment without declaration

A variable can be assigned its value with destructuring separate from its declaration.

var a, b;

({a, b} = {a: 1, b: 2});

The ( .. ) around the assignment statement is required syntax when using object literal destructuring assignment without a declaration.

{a, b} = {a: 1, b: 2} is not valid stand-alone syntax, as the {a, b} on the left-hand side is considered a block and not an object literal.

However, ({a, b} = {a: 1, b: 2}) is valid, as is var {a, b} = {a: 1, b: 2}

Assigning to new variable names

A variable can be extracted from an object and assigned to a variable with a different name than the object property.

var o = {p: 42, q: true};
var {p: foo, q: bar} = o;
 
console.log(foo); // 42 
console.log(bar); // true  

Default values

A variable can be assigned a default, in the case that the value pulled from the object is undefined.

var {a = 10, b = 5} = {a: 3};

console.log(a); // 3
console.log(b); // 5

Setting a function parameter’s default value

ES5 version

function drawES5Chart(options) {
  options = options === undefined ? {} : options;
  var size = options.size === undefined ? 'big' : options.size;
  var cords = options.cords === undefined ? {x: 0, y: 0} : options.cords;
  var radius = options.radius === undefined ? 25 : options.radius;
  console.log(size, cords, radius);
  // now finally do some chart drawing
}

drawES5Chart({
  cords: {x: 18, y: 30},
  radius: 30
});

ES2015 version

function drawES2015Chart({size = 'big', cords = {x: 0, y: 0}, radius = 25} = {}) {
  console.log(size, cords, radius);
  // do some chart drawing
}

drawES2015Chart({
  cords: {x: 18, y: 30},
  radius: 30
});

ref: https://developers.google.com/web/fundamentals/getting-started/primers/async-functions
Advertisements

One thought on “Async Iterators in JS and TS”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s