Search code examples
typescriptiterabletyping

iterating over 'iterables' in Typescript


Update changed the question according to @CherryDT's comment


I wrote an iterator over the keys and values of an iterable (in the Python sense, where dicts are considered iterable and I use Javascript objects in that sense):

type key = any;
type value = any;

function* items(
    iterable: Map<key, value> | Iterable<value> | Object
): IterableIterator<[key, value]> {

    if (iterable instanceof Map) {
        for (const entry of iterable.entries()) {
            yield entry;
        }
    }

    else if (iterable instanceof Array || iterable instanceof String || iterable instanceof Set) {
        let index = -1;
        for (const value of iterable) {
            yield [++index, value];
        }
    }

    else if (iterable instanceof Object) {
        for (const entry of Object.entries(iterable)) {
            yield entry;
        }
    }

    else {
        //@ts-ignore
        throw new Error(`Not implemented for ${iterable.constructor.name} type`);
    }
}

Can I get rid of the error without using the // @ts-ignore comment? The error is Property 'constructor' does not exist on type 'never'. I don't know how to fix this.


Solution

  • I got rid of the error by improving the final else branch. This answer helped. Typescript gives the error because simple types like number have no constructor, so instanceof or <simple type>.constructor.name won't work, but typeof <simple type> will.

    function* items(
        iterable: Map<key, value> | Iterable<value> | Object
    ): IterableIterator<[key, value]> {
    
        if (iterable instanceof Map) {
            for (const entry of iterable.entries()) {
                yield entry;
            }
        }
    
        else if (
            // sorted by usage frequency
            iterable instanceof Array
            || typeof iterable === 'string'
            || iterable instanceof Set
            || iterable instanceof String
        ) {
            let index = -1;
            for (const value of iterable) {
                yield [++index, value];
            }
        }
    
        else if (iterable instanceof Object) {
            for (const entry of Object.entries(iterable)) {
                yield entry;
            }
        }
    
        else {
            throw new Error(`Can not be used with '${typeof iterable}' type`);
        }
    }
    

    Usage example (when compiled to Javascript):

    function* items(iterable) {
        if (iterable instanceof Map) {
            for (const entry of iterable.entries()) {
                yield entry;
            }
        }
        else if (
            // sorted by usage frequency
            iterable instanceof Array
            || typeof iterable === 'string'
            || iterable instanceof Set
            || iterable instanceof String
        ) {
            let index = -1;
            for (const value of iterable) {
                yield [++index, value];
            }
        }
        else if (iterable instanceof Object) {
            for (const entry of Object.entries(iterable)) {
                yield entry;
            }
        }
        else {
            throw new Error(`Can not be used with '${typeof iterable}' type`);
        }
    }
    
    let iterables = [
        'hi',
        new String('ho'),
        [1, true, 'blurp'],
        new Set(['foo', 'bar', 'foo']),
        {1:'foo', 'bar':null},
        new Map([[1,'foo'], ['bar', null]]),
    ];
    
    for (const iterable of iterables) {
        console.log(iterable.constructor.name);
        for(const [key, value] of items(iterable)) {
            console.log(key, value);
        }
    }