TypeScript:使用 0 个或多个参数调用重载的 asyncPipe

J. *_*ers 5 functional-programming typescript typescript-typings

我有一个asyncPipe这样的功能:

export function asyncPipe<A, B>(
  ab: (a: A) => MaybePromise<B>
): (a: MaybePromise<A>) => Promise<B>;
export function asyncPipe<A, B, C>(
  ab: (a: A) => MaybePromise<B>,
  bc: (b: B) => MaybePromise<C>
): (a: MaybePromise<A>) => Promise<C>;
export function asyncPipe<A, B, C, D>(
  ab: (a: A) => MaybePromise<B>,
  bc: (b: B) => MaybePromise<C>,
  cd: (c: C) => MaybePromise<D>
): (a: MaybePromise<A>) => Promise<D>;
export function asyncPipe<A, B, C, D, E>(
  ab: (a: A) => MaybePromise<B>,
  bc: (b: B) => MaybePromise<C>,
  cd: (c: C) => MaybePromise<D>,
  de: (d: D) => MaybePromise<E>
): (a: MaybePromise<A>) => Promise<E>;

export function asyncPipe(...fns: Function[]) {
  return (x: any) => fns.reduce(async (y, fn) => fn(await y), x);
}
Run Code Online (Sandbox Code Playgroud)

我想这样称呼它:

const createRoute = (...middleware: Middleware[]) => async (
  request: Response,
  response: Request
) => {
  try {
    await asyncPipe(...middleware)({
      request,
      response
    });
  } catch (e) {
    console.error(e);
    response.status(500);
    response.json({
      error: "Internal Server Error"
    });
  }
};
Run Code Online (Sandbox Code Playgroud)

whereMiddleware是函数的一种类型,它接收至少具有键requestresponse的对象,但可能会向该对象添加更多数据。

{
  request,
  response,
  user, //  added by some middleware
  db, //  added by another middleware
  /* ... more keys added by middleware */
}
Run Code Online (Sandbox Code Playgroud)

打字稿抱怨说 Expected 1-6 arguments, but got 0 or more.在我调用的行中asyncPipe。我怎么能告诉它,这middleWare总是至少一个参数,或者重载它,所以它也接受 0 个参数?

更新:

我试过 Rob 的回答是这样的:

export function asyncPipe<A>(...fns: Function[]): MaybePromise<A>;
Run Code Online (Sandbox Code Playgroud)

但这可以防止我所有的asyncPipe特定测试失败。顺便说一句,测试看起来像这样:

import { describe } from "riteway";

import { asyncPipe } from "./asyncPipe";

const asyncInc = (n: number) => Promise.resolve(n + 1);
const inc = (n: number) => n + 1;

describe("asyncPipe()", async assert => {
  assert({
    given: "a promise",
    should: "pipe it",
    actual: await asyncPipe(asyncInc)(1),
    expected: 2
  });

  assert({
    given: "two promises",
    should: "pipe them",
    actual: await asyncPipe(asyncInc, asyncInc)(1),
    expected: 3
  });

  assert({
    given: "three promises",
    should: "pipe them",
    actual: await asyncPipe(asyncInc, asyncInc, asyncInc)(1),
    expected: 4
  });

  assert({
    given: "promises mixed with synchronous function",
    should: "pipe them",
    actual: await asyncPipe(asyncInc, inc, asyncInc)(1),
    expected: 4
  });

  {
    const throwInc = (n: number) => Promise.reject(n + 1);

    assert({
      given: "promises where one throws",
      should: "pipe them",
      actual: await asyncPipe(asyncInc, throwInc, asyncInc)(1).catch(x => x),
      expected: 3
    });
  }
});
Run Code Online (Sandbox Code Playgroud)

bla*_*e20 1

我做了一些额外的研究,并在这里获取了一些答案来做出我自己的答案。您现在有两个选择:

选项1

一种方法是将函数的代码写入函数asyncPipecreateRoute

const createRoute = (...middleware: Middleware[]) => async (
  request: Response,
  response: Request
) => {
  try {
    middleware.reduce(async (y, fn) => fn(await y), { request, response });
  } catch (e) {
    console.error(e);
    response.status(500);
    response.json({
      error: "Internal Server Error"
    });
  }
};
Run Code Online (Sandbox Code Playgroud)

这肯定会起作用,但我不确定你是否有这种类型检测。尝试第二个选项:

选项2

第二种方法稍微复杂一些。它将涉及复杂的类型,但我认为这正是您想要的。(此代码取自Arturs的答案并修改)

type MaybePromise<T> = T | Promise<T>

export function asyncPipe<A, B>(
  ab: (a: A) => MaybePromise<B>
): (a: A) => Promise<B>;
export function asyncPipe<A, B, C>(
  ab: (a: A) => MaybePromise<B>,
  bc: (b: B) => MaybePromise<C>
): (a: A) => Promise<C>;
export function asyncPipe<A, B, C, D>(
  ab: (a: A) => MaybePromise<B>,
  bc: (b: B) => MaybePromise<C>,
  cd: (c: C) => MaybePromise<D>
): (a: A) => Promise<D>;
export function asyncPipe<A, B, C, D, E>(
  ab: (a: A) => MaybePromise<B>,
  bc: (b: B) => MaybePromise<C>,
  cd: (c: C) => MaybePromise<D>,
  de: (d: D) => MaybePromise<E>,
): (a: A) => Promise<E>;

export function asyncPipe<FS extends any[]>(...fns: AsyncParams<FS>): AsyncPipeReturnType<FS>
export function asyncPipe (...fns: AsyncParams<any[]>) {
  if (fns.length === 0) return () => Promise.resolve(null)
  return (x: Parameters<typeof fns[0]>[0]) => fns.reduce(async (y, fn) => fn(await y), x)
}

type Callback = (a: any) => MaybePromise<unknown>;
type FunToReturnType<F> = F extends Callback ? ReturnType<F> extends Promise<infer U> ? U : ReturnType<F> : never;
type EmptyPipe = (a: never) => Promise<never>

type AsyncPipeReturnType<FS extends Callback[], P = Parameters<FS[0]>[0]> = 
  FS extends [...infer _, infer Last] ? (a: P) => Promise<FunToReturnType<Last>> : EmptyPipe;

type AsyncParams<FS extends Callback[], P = Parameters<FS[0]>[0]> =
  FS extends [infer H, ...infer Rest] ?
    H extends (p: P) => unknown ?
      Rest extends Callback[] ?
        [H, ...AsyncParams<Rest, FunToReturnType<H>>] 
          : [{ error: "__A_PARAMETER_NOT_A_FUNCTION__" }, ...Rest]
            : [{ error: "__INCORRECT_FUNCTION__", provided: H, expected_parameter: P }, ...Rest]
              : FS;
Run Code Online (Sandbox Code Playgroud)

你可以在这里尝试一下

无法自动推断 FS 类型。

如果您希望下一个中间件了解前一个中间件,则必须稍微修改类型并将其添加到函数中createRoute