array-from-with-iterator.js   [plain text]


function shouldBe(actual, expected) {
    if (actual !== expected)
        throw new Error('bad value: ' + actual);
}

function shouldThrow(func, message) {
    var error = null;
    try {
        func();
    } catch (e) {
        error = e;
    }
    if (!error)
        throw new Error("not thrown.");
    if (String(error) !== message)
        throw new Error("bad error: " + String(error));
}

var originalArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

var array = Array.from(originalArray.values());
shouldBe(array.length, originalArray.length);
for (var i = 0; i < array.length; ++i) {
    shouldBe(array[i], originalArray[i]);
}

function createIterator(callback) {
    var array = [0,1,2,3,4,5];
    var iterator = array[Symbol.iterator]();
    iterator.return = function () {
        iterator.returned = true;
        if (callback)
            return callback(this);
        return { done: true, value: undefined };
    };
    iterator.returned = false;
    return iterator;
}

var iterator = createIterator();
var result = Array.from(iterator);
shouldBe(result.length, 6);
for (var i = 0; i < 6; ++i) {
    shouldBe(result[i], i);
}
shouldBe(iterator.returned, false);

// mapFn raises an error.
var iterator = createIterator();
shouldThrow(function () {
    var result = Array.from(iterator, function () {
        throw new Error('map func');
    });
}, "Error: map func");
shouldBe(iterator.returned, true);

// mapFn raises an error and iterator.return also raises an error.
var iterator = createIterator(function () {
    throw new Error('iterator.return');
});

// An error raised in iterator.return is discarded.
shouldThrow(function () {
    var result = Array.from(iterator, function () {
        throw new Error('map func');
    });
}, "Error: map func");
shouldBe(iterator.returned, true);

// iterable[Symbol.iterator] is not a function.
shouldThrow(function () {
    var iterator = [].values();
    iterator[Symbol.iterator] = {};
    Array.from(iterator);
}, "TypeError: Array.from requires that the property of the first argument, items[Symbol.iterator], when exists, be a function");

// iterable[Symbol.iterator] raises an error.
shouldThrow(function () {
    var iterable = [];
    iterable[Symbol.iterator] = function () {
        throw new Error("iterator");
    };
    Array.from(iterable);
}, "Error: iterator");

// iterable[Symbol.iterator] lookup is only once.
(function () {
    var iterable = [0, 1, 2, 3, 4, 5];
    var count = 0;
    var iteratorCallCount = 0;
    Object.defineProperty(iterable, Symbol.iterator, {
        get() {
            ++count;
            return function () {
                ++iteratorCallCount;
                return this.values();
            };
        }
    });
    var generated = Array.from(iterable);
    shouldBe(generated.length, iterable.length);
    for (var i = 0; i < iterable.length; ++i) {
        shouldBe(generated[i], iterable[i]);
    }
    shouldBe(count, 1);
    shouldBe(iteratorCallCount, 1);
}());

// The Symbol.iterator method of the iterator generated by iterable[Symbol.iterator] is not looked up.
(function () {
    var iterable = [0, 1, 2, 3, 4, 5];
    var count = 0;
    iterable[Symbol.iterator] = function () {
        ++count;
        var iterator = this.values();
        Object.defineProperty(iterator, Symbol.iterator, {
            get() {
                throw new Error('iterator[@@iterator] is touched');
            }
        });
        return iterator;
    };
    var generated = Array.from(iterable);
    shouldBe(generated.length, iterable.length);
    for (var i = 0; i < iterable.length; ++i) {
        shouldBe(generated[i], iterable[i]);
    }
    shouldBe(count, 1);
}());