FatalCatharsis FatalCatharsis - 4 months ago 71
Javascript Question

Typescript: specifying multiple callback types in a union

I am converting some stuff I use to typescript, and I'm at a bit of a loss. I made a generic foreach function that accepts arrays and objects, and takes a few different types of callbacks to handle the iteration. However, I don't know how to specify the type if it accepts a few different callbacks. I need to be able to return boolean or void from the function, and it should be able to pass in (any), (any, int), or (string, any). This is what I've got so far.

function foreach(obj : Array<any> | Object, func : (
((any) => boolean) |
((any) => void) |
((any, int) => boolean) |
((any, int) => void) |
((string, any) => boolean) |
((string, any) => void)
))
{
// if obj is an array ...
if(Object.prototype.toString.call(obj) === '[object Array]') {
// if callback def1
if(func.length == 1) { // error: property length does not exist on type 'any[] | Object'
for(let i = 0; i < obj.length; i++) {
if(typeof func === "function") {
if(!func(obj[i])) break; // error: Cannot invoke an expression whose type lacks a call signature
}
}
// if callback def2
} else if(func.length == 2) { // error: property length does not exist on type 'any[] | Object'
for(let i = 0; i < obj.length; i++) {
if(!func(obj[i], i)) break; // error: Cannot invoke an expression whose type lacks a call signature
}
}
// if obj is an object ...
} else if(Object.prototype.toString.call(obj) == '[object Object]') {
// if callback def1
if(func.length == 1) {
for(let key in obj) {
if(!obj.hasOwnProperty(key)) continue;
if(!func(obj[key])) break; // error: Cannot invoke an expression whose type lacks a call signature
}
// if callback def3
} else if(func.length == 2) {
for(let key in obj) {
if(!obj.hasOwnProperty(key)) continue;
if(!func(key, obj[key])) break; // error: Cannot invoke an expression whose type lacks a call signature
}
}
}
};

Answer

there you go:

function foreach<T>(obj : T[], func: (item: T) => void)
function foreach<T>(obj : T[], func: (item: T) => boolean)
function foreach<T>(obj : T[], func: (item: T, index: number) => boolean)
function foreach<T>(obj : T[], func: (item: T, index: number) => void)
function foreach<T>(obj : {[key: string]: T}, func: (item: T) => boolean)
function foreach<T>(obj : {[key: string]: T}, func: (item: T) => void)
function foreach<T>(obj : {[key: string]: T}, func: (key: string, item: T) => boolean)
function foreach<T>(obj : {[key: string]: T}, func: (key: string, item: T) => void)
function foreach<T>(obj : T[] | {[key: string]: T}, func : (item : T | string, index ?: number | T) => (boolean | void))
{
    if(Object.prototype.toString.call(obj) === '[object Array]') {
        let arr = <any>obj as T[];
        if(func.length == 1) {
            let cb = <any>func as (item: T) => boolean; 
            for(let i = 0; i < arr.length; i++) if(!cb(arr[i])) break;
        } else if(func.length == 2) {
            let cb = <any>func as (item: T, index: number) => boolean;
            for(let i = 0; i < arr.length; i++) if(!cb(obj[i], i)) break;
        }
    } else if(Object.prototype.toString.call(obj) == '[object Object]') {
        let arr = obj as {[key: string]: T};
        if(func.length == 1) {
            let cb = <any>func as (item: T) => boolean;
            for(let key in obj) {
                if(!obj.hasOwnProperty(key)) continue;
                if(!cb(obj[key])) break;
            }
        } else if(func.length == 2) {
            let cb = func as (key: string, item: T) => boolean;
            for(let key in obj) {
                if(!obj.hasOwnProperty(key)) continue;
                if(!cb(key, obj[key])) break;
            }
        }
    }
};

example of usage: enter image description here enter image description here

you can take advantage of intellisense: enter image description here