TypescripttapAsync関数が正しいタイピングを設定できません

Aug 24 2020

typescriptを使用して、いくつかのES6非同期関数を一緒にタップできるようにしたいと思います。tap関数は、tapped関数から何も返されない場合は引数を返す必要がありますが、tapped関数から何かが返される場合は戻り値をタップします。

typescriptの遊び場の例

入力しなくても動作させることができますが、タイプの設定に問題があります。Javascriptで機能するコードの例については、スニペットを参照してください。

タップされた関数は、x値を使用して呼び出され、x => fn(x)チェーンされて戻り値yまたはタップされた値のいずれかを返します。x x => fn(x).then(y => y || x)

any型を使用するこの最初のバージョンは機能しますが、タップされた関数で型を処理することに具体的に取り組むとエラーが発生します。

const tapAsync = (fn: (x: any) => Promise<any>) => (
  x: any
): Promise<any> => fn(x).then((y: any) => y || x)

より具体的にするXために、最初の引数とYtapped関数の戻り値に2つのジェネリックを使用しています。

const tapAsync = (fn: <X>(x: X) => Promise<X>) => (
  x: X
): Promise<Y|X> => fn(x).then(<Y>(y: Y) => y || x)

tapAsyncを使用して関数を呼び出すと、次のエラーが発生します。

src/index.ts:45:18 - error TS2345: Argument of type '({ foo }: { foo: any; }) => Promise<void>' is not assignable to parameter of type '<X>(x: X) => Promise<X>'.
  Types of parameters '__0' and 'x' are incompatible.
    Type 'X' is not assignable to type '{ foo: any; }'.

45   .then(tapAsync(one))
                    ~~~
src/index.ts:46:18 - error TS2345: Argument of type '({ foo }: { foo: any; }) => Promise<{ foo: any; bar: string; }>' is not assignable to parameter of type '<X>(x: X) => Promise<X>'.
  Types of parameters '__0' and 'x' are incompatible.
    Type 'X' is not assignable to type '{ foo: any; }'.

46   .then(tapAsync(two))
                    ~~~
src/index.ts:47:18 - error TS2345: Argument of type '({ foo, bar }: { foo: any; bar: any; }) => Promise<void>' is not assignable to parameter of type '<X>(x: X) => Promise<X>'.
  Types of parameters '__0' and 'x' are incompatible.
    Type 'X' is not assignable to type '{ foo: any; bar: any; }'.

47   .then(tapAsync(three))

タップされた関数のtypescriptに型を設定していませんが、2番目の関数2でジェネリック型を使用しようとしましたが、それも機能しません

async function two<X>({ foo }): Promise<X> {
  console.log('two', foo)
  return {
    foo,
    bar: 'bar'
  }
}

async function one({ foo }) {
  console.log('one', foo)
}

async function two({ foo }) {
  console.log('two', foo)
  return {
    foo,
    bar: 'bar'
  }
}

async function three({ foo, bar }) {
  console.log('three', foo, bar)
}

const tapAsync = fn => x => fn(x).then(y => y || x)

Promise.resolve({ foo: 'foo' })
  .then(tapAsync(one))
  .then(tapAsync(two))
  .then(tapAsync(three))

助けてくれてありがとう!

==============編集2020-09-01 ====================

私はコードで遊んでいて、タイプをもう少し具体化しましたが、同じ形状であるにもかかわらず、新しいオブジェクトを返すときに2つの関数でエラーが発生します。

新しいtypescript遊び場の例

const tapAsync = <X, Y>(fn: (x: X) => Promise<Y|void>) => 
  (x: X): Promise<X|Y> => 
    fn(x).then((y: Y|void) => y || x)

回答

1 NunoSousa Sep 01 2020 at 09:45

オーバーロードでうまくいくと思います。そして型推論は正しいように見えます。基本的に、これにより、tsコンパイラは(x:X)=>Promise<void | Y>asリターンがないことを推測できます。これが起こったのは、戻ってこない非同期function :Promise<void> Yへの最初の呼び出しの後、Yがそのタイプであると推測されたため、Promise.then(...Xをvoidとしてフィードしようとするためです。次の呼び出しで引数が発生し、期待(x: void | Args)=>....と互換性のないが発生し(x: Args)=>...ます。

オーバーロードであなたの例のこの遊び場をチェックしてください

function tapAsync <X,Y>(fn: (x:X)=>Promise<void>): (x:X)=>Promise<X>;
function tapAsync <X,Y>(fn: (x:X)=>Promise<Y>): (x:X)=>Promise<Y>;
function tapAsync <X, Y>(fn: (x: X) => Promise<Y|void>) {
  return (x: X) => fn(x).then(y => y||x )
}

編集:私の答えを読み直して、私は言及し(y: void| Y )=> Y||Xませんでした、原因は、最初のXがボイドでなければボイドを返すことができないと推論できないことです。