2017-09-09 12 views
1

ジェネリックスをTypeScript定義に適用する際に問題があります。TypeScriptジェネリックコールバックの最後の引数を持つ関数を受け入れる

の定義は以下のとおりです。

export function readFile(path: PathLike | number, options: { encoding?: null; flag?: string; } | undefined | null, callback: (err: NodeJS.ErrnoException, data: Buffer) => void): void; 
export function readFile(path: PathLike | number, options: { encoding: string; flag?: string; } | string, callback: (err: NodeJS.ErrnoException, data: string) => void): void; 
export function readFile(path: PathLike | number, options: { encoding?: string | null; flag?: string; } | string | undefined | null, callback: (err: NodeJS.ErrnoException, data: string | Buffer) => void): void; 

export function readFile(path: PathLike | number, callback: (err: NodeJS.ErrnoException, data: Buffer) => void): void; 

あなたは、彼らが2〜3の引数を受け入れる見ることができ、そして最後の1は常にコールバックです。

今私の総称である:

function cbCall<Ret, Arg1, Arg2>(
    fun: (arg1: Arg1, arg2: Arg2, cb: (error: any, result: Ret) => any) => any, 
    obj: any, 
    arg1: Arg1, 
    arg2: Arg2 
): Context<Ret>; 

それは3つの引数を持つ関数(コールバックされ、最後の1)私はそれが最初の3つの定義の一つにマッチする推測を予期しているため。私はこのようなcbCall使用しようとするただし、:

CL.cbCall(
    fs.readFile, 
    fs, 
    'filename', 
    { encoding: 'utf-8' } 
); 

を私はエラーを取得:

Argument of type '{ encoding: string; }' is not assignable to parameter of type '(err: ErrnoException, data: Buffer) => void'. 

だから何とかそれはarg2cbをすることになっているものであることを期待しています。

テンプレートパラメータArg2を削除し、それをobjectに置き換えても機能しますが、これは私の使用例で十分ではありません。

なぜそれが起こり、私が達成しようとしていることを達成できるかどうかを説明してください。

編集:cbCall<string, string, object>(...)も使用しますが、もう一度、目的を破ります。


編集:また、それを修正した最初の二行の順序を変更する

function fun(arg: string, cb: (err: string, res: string) => void); 
function fun(cb: (err: string, res: string) => void): void; 
function fun(arg: any, cb?: any): void { 
    // noop 
} 

function call<Ret, Arg>(fun: (arg: Arg, cb: (err: string, res: Ret) => void) => any, arg: Arg) { 
    // noop 
} 

// this works 
call<string, string>(fun,'test'); 

// Argument of type '"test"' is not assignable to parameter of type '(err: string, res: string) => void'. 
call(fun,'test'); // 

:私はこれまで例を簡素化。 しかし、最初に一致する宣言は使用されず、最後の宣言が使用されるべきではありませんか?

+1

この回答を終了する時間がない場合に備えて、ここにリンクをドロップする:https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#3117-type-in​​ference –

答えて

3

タイプパラメータの割り当ては、過負荷の解決前に行われ、失敗した場合はバックトラックしないため、異なる過負荷を選択することで回避できるエラーが発生することがあります。私は型パラメータマッチングアルゴリズムの良い説明をまだ見つけていないが、このissueはそれを少し説明している。

あなたfunは1つの過負荷を持っているので、少なくとも、特定のものを想定しているとして、readFileの最後のオーバーロードは、選択されてしまいます。型エラーの回避策として、あなたは希望を提供することができ、あなたがcbCallを呼び出すモジュールの中で自分自身をオーバーロード:

declare module 'fs' { 
    function readFile(path: fs.PathLike | number, options: { encoding?: string | null; flag?: string; } | string | undefined | null, callback: (err: NodeJS.ErrnoException, data: string | Buffer) => void): void; 
} 

それはしかしまだ素晴らしいではありません。私はタイプチェッカー(issue)をクラッシュした後、ジェネリック薬で少し控えめになった。 1つのリリースで動作する複雑なソリューションは、しばしば次のリリースに入り込みます。多くの進展が見られないオープンな問題がかなりあります。うまくいけば、将来的にはより安定します。

EDIT:別のオプションを考えました。コールバック関数を使用すると、options引数なしですべての共有とcbCallに最後の過負荷を渡す場合は、funの種類にその過負荷を追加することができます。

interface CallbackFun<Arg1, Arg2, Ret> { 
    (arg1 : Arg1, arg2 : Arg2, cb : (error : any, result : Ret) => any) : any 
    (arg1 : Arg1, cb : (error : any, result : Ret) => any) : any 
} 

cbCallの定義にfun: CallbackFun<Arg1, Arg2, Ret>を使用しています。

+0

ありがとう素晴らしい答え。 「readFileの最後のオーバーロードが選択されます。これは、最も特殊なものではないと思われます」 - それは私には起こりませんでした。それは意味をなさないと思います。私はこの場合テンプレート引数を明示的に渡すことに決めました。それは考えにくいです。 –

関連する問題