Search code examples
javascriptarraysecmascript-6ecmascript-2016

Internal *current value* of Array


I'm creating an array that iterates asynchronously (for fun). This works fine:

class AsyncArray extends Array {
    constructor() {
        super();
        this.x = 0;
    }

    [Symbol.iterator]() {
        return {
            next: () => {
                let promise = new Promise(resolve => setTimeout(
                    () => resolve(this.x++), 1000)
                );
                return {done: this.x >= this.length, value: promise};
            }
        };
    }
}

async () => {
    for (let x of AsyncArray.of(1, 2, 3)) {
        let value = await x;
        console.log(value);
    }
}();

However, this prints out 0...1...2 because I'm keeping track of the current counter on my own and initializing it to x.

Is there any way to get the current iterator value internal to the Array? I would also need to be able to properly determine the done value.


Solution

  • I guess you don't want the counter internal to your array, but rather to your iterator. Use a local variable in the method for that:

    [Symbol.iterator]() {
        var x = 0;
        return {
            next: () => {
                let promise = new Promise(resolve =>
                    setTimeout(() => resolve(this[x++]), 1000)
                );
                return {done: x >= this.length, value: promise};
            }
        };
    }
    

    The easiest way to write iterators though is by using a generator function:

    [Symbol.iterator]*() {
        for (var x = 0; x < this.length; x++)
             yield new Promise(resolve =>
                 setTimeout(() => resolve(this[x]), 1000)
             );
    }
    

    That will take care of the correct done value as well (and won't "return" a promise that resolves with undefined).


    An alternative that would completely avoid tracking state in a local variable or instance property would be to make use of the standard array iterator:

    [Symbol.iterator]() {
        var vals = super[Symbol.iterator]();
        var it = Object.create(Object.getPrototypeOf(vals)); // an array iterator
        it.next = () => {
            var res = vals.next();
            if (!res.done)
                return {done: false, value: new Promise(resolve =>
                    setTimeout(() => resolve(res.value), 1000)
                )};
            return res;
        };
        return it;
    }