Code Monkey home page Code Monkey logo

async-iteration's Introduction

Asynchronous Iterators for ECMAScript

Overview and Motivation

The iterator interface (introduced in ECMAScript version 6) is a sequential data access protocol which enables the development of generic and composable data consumers and transformers. Generator functions (also introduced in ECMAScript 6) provide a convenient way to write iterator-based data sources using resumable functions.

interface Iterator {
    next(value) : IteratorResult;
    [optional] throw(value) : IteratorResult;
    [optional] return(value) : IteratorResult;
}

interface IteratorResult {
    value : any;
    done : bool;
}

Since both the next value in the sequence and the "done" state of the data source must be known at the time that the iterator method returns, iterators are only suitable for representing synchronous data sources. While many data sources encountered by the Javascript programmer are synchronous (such as in-memory lists and other data structures), many are not. For instance, any data source which requires IO access will be typically represented using a callback or event-based asynchronous API. Unfortunately, iterators cannot be used to represent such data sources.

In order to provide a generic data access protocol for asynchronous data sources, we introduce the AsyncIterator interface, an asynchronous iteration statement, and async generator functions.

The AsyncIterator Interface

The AsyncIterator interface is identical to the Iterator interface, except that each of the iterator methods returns a promise for an iterator result pair.

NOTE: We must return a promise for the {next, done} pair because both the next value and the "done" state of the iterator are potentially unknown at the time the iterator method returns.

interface AsyncIterator {
    next(value) : Promise<IteratorResult>;
    [optional] throw(value) : Promise<IteratorResult>;
    [optional] return(value) : Promise<IteratorResult>;
}

For example:

asyncIterator.next().then(value => console.log(value));

Furthermore, we introduce a new symbol used for obtaining an async iterator from a given object.

interface AsyncIterable {
    [Symbol.asyncIterator]() : AsyncIterator
}

Implicit in the concept of the async iterator is the concept of a request queue. Since iterator methods may be called many times before the result of a prior request is resolved, each method call must be queued internally until all previous request operations have completed.

The Async Iteration Statement

We introduce a variation of the for-of iteration statement which iterates over AsyncIterator objects.

IterationStatement :
    for await ( LeftHandSideExpression of AssignmentExpression ) Statement

IterationStatement :
    for await ( var ForBinding of AssignmentExpression ) Statement

IterationStatement :
    for await ( ForDeclaration of AssignmentExpression ) Statement

For example:

for await (let line of readLines(filePath)) {
    print(line);
}

Async for-of statements are only allowed within async functions and async generator functions.

During execution, an async iterator is created from the data source using the Symbol.asyncIterator method.

Each time we access the next value in the sequence, we implicitly await the promise returned from the iterator method.

Async Generator Functions

Async generator functions are similar to generator functions, with the following differences:

  • When called, async generator functions return an object implementing the AsyncIterator interface.
  • Await expressions and for-await statements are allowed.
  • Yielded promises are implicitly unwrapped before they are packed into an IteratorResult object.
  • The behavior of yield* is modified to support delegation to async iterators.

For example:

async function *readLines(path) {

    let file = await fileOpen(path);

    try {

        while (!file.EOF)
            yield file.readLine();

    } finally {

        await file.close();
    }
}

Async Generator Function Rewrite

async function * <Name>? <ArgumentList> <Body>

=>

function <Name>? <ArgumentList> {

    return asyncGeneratorStart(function*() <Body>.apply(this, arguments));
}

To desugar await within async generator functions, we introduce the IterAwaitResult object. An IterAwaitResult is an iterator result object which is branded to indicate that it is the result of an await expression and not a yield expression. The async generator "runner" uses this brand to tell whether it should resolve the result value and continue or return the result value to the generator client.

Such an object would not be necessary in the actual specification.

function asyncGeneratorStart(generator) {

    let current = null,
        queue = [];

    return {

        next(value)   { return enqueue("next", value) },
        throw(value)  { return enqueue("throw", value) },
        return(value) { return enqueue("return", value) },
        [Symbol.asyncIterator]() { return this },
    };

    function enqueue(type, value) {

        return new Promise((resolve, reject) => {

            queue.push({ type, value, resolve, reject });
            next();
        });
    }

    function next() {

        if (current || queue.length === 0)
            return;

        current = queue.shift();
        resume(current.type, current.value);
    }

    function settle(type, value) {

        let capability = current;
        current = null;

        switch (type) {

             case "throw":
                capability.reject(value);
                break;

            case "return":
                capability.resolve({ value, done: true });
                break;

            default:
                capability.resolve({ value, done: false });
                break;
        }

        next();
    }

    function resume(type, value) {

        let result;

        try {

            result = generator[type](value);

            if (IsIterAwaitResultObject(result)) {

                Promise.resolve(result.value).then(
                    x => resume("next", x),
                    x => resume("throw", x));

            } else {

                Promise.resolve(result.value).then(
                    x => settle(result.done ? "return" : "normal", x),
                    x => settle("throw", x));
            }

        } catch (x) {

            settle("throw", x);
        }
    }
}

async-iteration's People

Contributors

zenparsing avatar

Watchers

 avatar  avatar

Forkers

jdjkelly

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.