修复“类型实例化太深并且可能是无限的。” 在组合函数中

Tho*_*ggi 5 composition infinite-loop control-flow function-composition typescript

我创建了一个将函数链接在一起的函数。我创建了一堆通用类型来正确关联结果函数可以具有的参数。您可以在此处阅读有关此范例的更多信息、我正在寻找的内容以及我创建它的原因。

一切正常……到了一定程度。由于我生成类型的方式,当链变得太长时,TypeScript 会失败。我正在寻找一种方法来缓解这个问题。

我收到此错误:

Type instantiation is excessively deep and possibly infinite.

毫无疑问,这对于 TypeScript 来说太多了,而且随着每个函数的添加,类型映射呈指数级增长。

我意识到这段代码相当复杂,所以我不是在寻找完整的技术解决方案,只是寻找一种更符合 TypeScript 的/算法更简单的方法来做同样的事情。

namespace Poly {
    export type Omit<T, K extends string | number | symbol> = { [P in Exclude<keyof T, K>]: T[P]; }
}

namespace Use {
    type ThenArg<T> = T extends Promise<infer U> ? U : T
    type RelatedThen<A, B> = A extends Promise<infer U> ? Promise<B> : B
    export type Func = (...args: any[]) => any
    export type Return<A extends Func, B> = RelatedThen<ReturnType<A>, B>
    export type CallbackValue<A extends Func> = ThenArg<ReturnType<A>>
}

namespace Profound {
    export type Func = (...arg: any) => any
    export type SingleArgFunc = (arg: any) => any
    export type SingleArgFuncs = { [k: string]: SingleArgFunc }
    export namespace Params {
        type Func = (arg: any) => any
        type Funcs = { [k: string]: Func }
        type FirstArg<T extends any> =
            T extends [infer R, ...any[]] ? R :
            T extends [] ? undefined :
            T;
        type ThenArg<T> = T extends Promise<infer U> ? U : T

        type UnionToIntersectionValues<U, K extends keyof U = keyof U> =
            ([K] extends [never]
                ? unknown
                : K extends unknown
                ? (k: U[K]) => void
                : never
            ) extends (k: infer I) => void ? I : never


        export type FnsBoth<T extends Funcs> = UnionToIntersectionValues<{
            [K in keyof T]: Parameters<T[K]>[0] extends undefined ?
            { [KK in K]?: ThenArg<ReturnType<T[K]>> } :
            { [KK in K]: ThenArg<ReturnType<T[K]>> } | FirstArg<Parameters<T[K]>>
        }>

        export type FnsNoArg<T extends Funcs> = UnionToIntersectionValues<ObjEmptyNever<ObjWithoutNever<{
            [K in keyof T]: Parameters<T[K]>[0] extends undefined ?
            { [KK in K]?: ThenArg<ReturnType<T[K]>> } :
            never
        }>>>

        export type FnsHasArg<T extends Funcs> = UnionToIntersectionValues<ObjEmptyNever<ObjWithoutNever<{
            [K in keyof T]: Parameters<T[K]>[0] extends undefined ?
            never :
            { [KK in K]: ThenArg<ReturnType<T[K]>> } | FirstArg<Parameters<T[K]>>
        }>>>


        export type Keys<T> = { [K in keyof T]: T[K] }[keyof T]
        export type ObjEmptyNever<T> = Keys<T> extends never ? never : T
        export type ObjKeysWithoutNever<T> = { [K in keyof T]: T[K] extends never ? never : K }[keyof T]
        export type ObjWithoutNever<T> = Pick<T, ObjKeysWithoutNever<T>>;
        export type ObjClean<T> = ObjEmptyNever<ObjWithoutNever<T>>

        export type FnsOverride<T> = { [K in keyof T]: T[K] extends Func ? ThenArg<ReturnType<T[K]>> : never }
        export type FnArguments<T> = ObjClean<{ [K in keyof T]: T[K] extends (arg: any) => any ? Parameters<T[K]>[0] extends undefined ? never : Parameters<T[K]>[0] : never }>

        export type Value<T extends Funcs> = FnArguments<T> extends never ? Partial<FnsOverride<T>> | void : FnsHasArg<T> | FnsNoArg<T>

        // export type Value<T extends Funcs> = FnArguments<T> extends never ? FnsBoth<T> | void : FnsBoth<T>

        // export type Value<T extends Funcs> = FnsHasArg<T> & FnsNoArg<T>
        // export type Value<T> = FnsBoth<T>
    }

    export namespace Callback {
        type ThenArg<T> = T extends Promise<infer U> ? U : T
        export type ObjReturn<T> = { [K in keyof T]: T[K] extends SingleArgFunc ? ThenArg<ReturnType<T[K]>> : never }
    }

    export namespace Return {
        export type IsPromise<T> = T extends Promise<infer I> ? T : never
        export type FnIsPromise<T> = T extends (...args: any) => any ? IsPromise<ReturnType<T>> : never
        export type FnIsPromiseFn<T> = T extends (...args: any) => any ? IsPromise<ReturnType<T>> extends never ? never : T : never
        // gets object keys in object, never if no keys
        export type Keys<T> = { [K in keyof T]: T[K] }[keyof T]
        // get object if has keys, never if no keys
        export type ObjEmptyNever<T> = Keys<T> extends never ? never : T
        // gets object keys without value set to never
        export type ObjKeysWithoutNever<T> = { [K in keyof T]: T[K] extends never ? never : K }[keyof T]
        // gets object without never keys
        export type ObjWithoutNever<T> = Pick<T, ObjKeysWithoutNever<T>>;
        // gets functions that return promises in object never if empty, without never properties
        export type ObjFnIsPromise<T> = ObjEmptyNever<ObjWithoutNever<{ [K in keyof T]: FnIsPromise<T[K]> }>>
        // removes keys from B in A
        export type ObjDiff<A, B> = Poly.Omit<A, keyof B>[keyof Poly.Omit<A, keyof B>]
        // gets promises from A and removes them if theyr'e in C
        export type ObjPromiseDiff<A, C> = ObjFnIsPromise<A> extends never ? never : ObjDiff<ObjFnIsPromise<A>, C>
        // if B returns promise, or if there are promises in A and don't match keys in C
        export type RetunsPromise<A, B, C> = FnIsPromiseFn<B> | ObjPromiseDiff<A, C>
        // if promise, provides inner promise type
        type ThenArg<T> = T extends Promise<infer U> ? U : T
        // gets return type from function, withou inner promise if any
        type ReturnValue<T extends SingleArgFunc> = T extends (args: any) => any ? ThenArg<ReturnType<T>> : never
        // gets all return types in an object of functions
        type ObjReturn<T extends SingleArgFuncs> = { [K in keyof T]: ReturnValue<T[K]> }
        // checks A diffed with C for promsies, if so wraps all ObjectReturns in promise 
        type ReturnNoCallback<A extends SingleArgFuncs, B, C> = ObjPromiseDiff<A, C> extends never ? ObjReturn<A> : Promise<ObjReturn<A>>
        // checks for promise, returns value of inner callback wrapped in promise
        type ReturnYesCallback<A, B extends SingleArgFunc, C> = RetunsPromise<A, B, C> extends never ? ReturnValue<B> : Promise<ReturnValue<B>>
        // checks if callback is available, before deligation of return 
        // export type Value<A, B, C> = unknown extends B ? ReturnNoCallback<A, B, C> : ReturnYesCallback<A, B, C>
        // for some reason have to check if B actually returns something because `B` is known
        export type Value<A extends SingleArgFuncs, B extends SingleArgFunc, C> = unknown extends ReturnValue<B> ? ReturnNoCallback<A, B, C> : ReturnYesCallback<A, B, C>
    }
}

export function isPromise(obj: any) {
    return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function'
}

function use<A extends Use.Func, G>(fn: A, callback: (e: Error | null, a: Use.CallbackValue<A>) => G) {
    return (...args: Parameters<A>): Use.Return<A, G> => {
        try {
            const v = fn(...args as any[])
            if (isPromise(v)) {
                return v.then((v: any) => callback(null, v)).catch((e: any) => callback(e, undefined as Use.CallbackValue<A>))
            }
            return callback(null, v) as Use.Return<A, G>
        } catch (e) {
            return callback(e, undefined as Use.CallbackValue<A>) as unknown as Use.Return<A, G>
        }
    }
}

export function placeholder<T>() {
    return <G extends string>(param: G) => {
        return (a: Record<G, T>): T => {
            throw new Error(`Need ${param}`)
        }
    }
}

const profoundRef = Symbol('Profound')

function isPlainObject(obj: any): obj is ({ [name: string]: any }) {
    return obj && obj.constructor === Object || false;
}

function pick(o: any, ...props: string[]) {
    return Object.assign({}, ...props.map(prop => ({ [prop]: o[prop] })));
}

function nest<T>(value: T): () => T {
    return () => {
        return value
    }
}

export function profound<A extends Profound.SingleArgFuncs, G, B extends (a: Profound.Callback.ObjReturn<A>) => G, C extends Profound.Params.Value<A>>(funcs: A, callback?: B) {
    function reduceObjectOfFuncs(funcs: any, input: any = {}) {
        if (!isPlainObject(input)) throw new Error('Needs to be plain object')
        // console.log(input)
        const keys = Object.keys(funcs)
        return keys.reduce((acq: Profound.Func, key: string) => {
            return use(acq, (acqError, acqValue) => {
                if (acqError) throw acqError
                if (acqValue && acqValue.hasOwnProperty(key)) return acqValue
                const isProfound = Boolean(funcs[key] &&
                    funcs[key].isProfound &&
                    funcs[key].isProfound === profoundRef &&
                    funcs[key].pass)
                const fn = (isProfound) ? funcs[key].pass : funcs[key]
                return use(fn, (fnError, fnValue) => {
                    if (fnError) throw fnError
                    const [value, data] = (isProfound) ? fnValue : [fnValue, {}]
                    return { ...acqValue, ...data, [key]: value }
                })(acqValue)
            })
        }, nest(input))()
    }
    // NOTE returns (callbackValue)
    // NOTE MARKER
    function profound(preInput: C): Profound.Return.Value<A, B, C> {
        return use(reduceObjectOfFuncs, (err, input) => {
            if (err) throw err
            const keys = Object.keys(funcs)
            if (!callback) return pick(input, ...keys)
            return use(callback as Profound.Func, (err, callbackValue) => {
                if (err) throw err
                return callbackValue
            })(input)
        })(funcs, preInput)
    }
    // NOTE returns ([callbackValue, reduceObjectOfFuncsValues])
    function pass(preInput: any): any {
        return use(reduceObjectOfFuncs, (err, input) => {
            if (err) throw err
            const keys = Object.keys(funcs)
            if (!callback) return [pick(input, ...keys), input]
            return use(callback as Profound.Func, (err, callbackValue) => {
                if (err) throw err
                return [callbackValue, input]
            })(input)
        })(funcs, preInput)
    }
    profound.isProfound = profoundRef
    profound.pass = pass
    return profound
}
Run Code Online (Sandbox Code Playgroud)

工作用途:

import { placeholder, profound } from '@reggi/profound'

const alpha = profound({}, ({ }) => 'hello world I\'m a profound')
// console.log(alpha()) // hello world I\'m a profound

const beta = profound({ alpha }, ({ alpha }) => `I am using alpha as a dependency (${alpha})`)
// console.log(beta()) // I am using alpha as a dependency (hello world I'm a profound)

const gamma = profound({ alpha, beta }, ({ alpha, beta }) => `
    Gamma needs alpha and beta to run.
    Rather than being very redundant and running both in here. They are fetched for use. 
    If you pass alpha or beta into gamma, they are not run. 
    This function also takes any arguments that alpha, or beta need.
    Profounds become async: 
        1. If their dependencies are async and are being run (input dependent)
        2. If this callback is async
    (${alpha} ${beta})
`)

const age = placeholder<number>()('age')

const delta = profound({ age, gamma }, ({ age, gamma }) => gamma.length + age)

console.log(delta({ age: 30 })) // 514
Run Code Online (Sandbox Code Playgroud)

当我创建一个非常长的链时,我收到此错误:

Type instantiation is excessively deep and possibly infinite.

import {profound} from './profound';

export const example0 = profound({}, () => 'anything');
export const example1 = profound({example0}, () => 'anything');
export const example2 = profound({example1}, () => 'anything');
export const example3 = profound({example2}, () => 'anything');
export const example4 = profound({example3}, () => 'anything');
export const example5 = profound({example4}, () => 'anything');
export const example6 = profound({example5}, () => 'anything');
export const example7 = profound({example6}, () => 'anything');
export const example8 = profound({example7}, () => 'anything');
export const example9 = profound({example8}, () => 'anything');
export const example10 = profound({example9}, () => 'anything');
export const example11 = profound({example10}, () => 'anything');
export const example12 = profound({example11}, () => 'anything');
export const example13 = profound({example12}, () => 'anything');
export const example14 = profound({example13}, () => 'anything');
export const example15 = profound({example14}, () => 'anything');
export const example16 = profound({example12, example13, example14, example15}, () => 'anything');
export const example17 = profound({example16, example13, example14, example15}, () => 'anything');
Run Code Online (Sandbox Code Playgroud)