【问题标题】:Type inference from synchronous or asynchronous parameters从同步或异步参数进行类型推断
【发布时间】:2021-02-20 02:42:28
【问题描述】:

我想定义一个“执行”方法,该方法根据其参数返回同步或异步结果:

type Callback = (...args: Arguments) => Result
const result: Result = execute(callback: Callback, args: Arguments)
type Callback = (...args: Arguments) => Promise<Result>
const result: Promise<Result> = execute(callback: Callback, args: Arguments)
type Callback = (...args: Arguments) => Result | Promise<Result>
const result: Promise<Result> = execute(callback: Callback, args: Promise<Arguments>)

我尝试这个没有成功:

sample code in TS Playground

console.clear()

type Callback<Arguments extends Array<any>, Result> =
| ((...args: Arguments) => Result)
| ((...args: Arguments) => Promise<Result>)

const execute = <Arguments extends Array<any>, Result> (callback: Callback<Arguments, Result>, args: Arguments | Promise<Arguments>): typeof args extends Promise<Arguments> ? Promise<Result> : ReturnType<typeof callback> => {
  if (args instanceof Promise) {
    return args.then(resolved_args => {
      const result = callback(...resolved_args)
      return result
    })
  }

  else {
    return callback(...((args ?? []) as Arguments))
  }
}

const async_demo = async (value: number) => {
  const a = await Promise.resolve(1)
  return a + value
}

const sync_demo = (value: number) => 1 + value
type SyncDemoResult = ReturnType<typeof sync_demo>

const result_1: number = execute(sync_demo, [1])
const result_2: Promise<number> = execute(sync_demo, Promise.resolve([1] as [number]))
const result_3: Promise<number> = execute(async_demo, [1])
const result_4: Promise<number> = execute(async_demo, [1])

有可能吗?

【问题讨论】:

  • 我对 TypeScript 的了解不够深入,无法知道它是否可能,但即使它可能也是可取的吗?有时是同步的,有时是异步的函数并不是一个好主意,而且肯定需要你在 TypeScript 中破坏类型安全才能尝试它。我建议您使用 executeAsync() 进行异步操作。
  • 使用这种技术,我可以在数据访问(DDD 中的存储库)同步或异步的客户端代码(同步)和服务器(异步)之间共享业务代码。通过使用“执行”注入存储库,我可以获得在客户端同步和在服务器上异步的方法。 Promise 在性能上的成本太高,而且有时在 React 应用程序中使用钩子会更复杂。
  • 但是,如果您的代码只是在 API 下方和 API 上方进行分支以处理异步和同步情况,并且您只是通过非类型化和非描述性的单一 API 填充所有内容,我不会看不到你得到了什么。如果根据同步和异步进行拆分,您仍然可以共享同样多的代码。我认为您为了完美的代码共享而扭曲了 API 的设计和使用。 IMO,这是错误的优先顺序。应优先考虑设计、使用和维护的清洁度。
  • 函数式编程的神奇之处:“执行”方法是currified,用于包装存储库访问:因此我可以创建在客户端(同步/存储)和服务器(异步)之间共享的业务方法/ 数据库)。我注入存储库访问方法来构建客户端或服务器代码。相同的代码,2 个签名,取决于注入的方法。 ;-)

标签: typescript asynchronous promise type-inference


【解决方案1】:

使用@aleksey-l 的解决方案,这是我将用于与异步或同步输入共享相同代码的解决方案:

Playground

type AnyFunc = (...args: any[]) => any;
type AnyAsyncFunc<T> = (...args: any[]) => Promise<T>;

function execute<U, T extends AnyAsyncFunc<U>>(callback: T, args: Promise<Parameters<T>>): Promise<U>
function execute<T extends AnyFunc>(callback: T, args: Parameters<T>): ReturnType<T>
function execute<T extends AnyFunc>(callback: T, args: Promise<Parameters<T>>): Promise<ReturnType<T>>
function execute<T extends AnyFunc>(callback: T, args: Parameters<T> | Promise<Parameters<T>>) {
  if (args instanceof Promise) {
    return args.then(resolved_args => callback(...resolved_args))
  }
  return callback(...args);
}

// --------------------------------------------------

const async_demo = async (value: number) => {
  const a = await Promise.resolve(1)
  return a + value
}

// --------------------------------------------------

const sync_demo = (value: number) => 1 + value

// --------------------------------------------------

const result_1: number = execute(sync_demo, [1])
const result_2: Promise<number> = execute(sync_demo, Promise.resolve([1] as [number]))
const result_3: Promise<number> = execute(async_demo, [1])
const result_4: Promise<number> = execute(async_demo, Promise.resolve([1] as [number]))

【讨论】:

  • AnyAsyncFunc 是多余的,因为它已经被AnyFunc 覆盖,ReturnType 将正确处理此问题
  • @aleksey-l: 不,没有它,返回的类型是 Promise>;有什么想法可以用另一种方式解决这个问题吗?
  • 你可以使用类似type UnpackPromise&lt;T&gt; = T extends Promise&lt;infer R&gt; ? R : T; typescriptlang.org/play?#code/…
【解决方案2】:

是的,这是可能的。下面定义了两个重载:第一个接受直接回调参数,第二个 - 承诺解析回调参数:

type AnyFunc = (...args: any[]) => any;

function execute<T extends AnyFunc>(callback: T, args: Parameters<T>): ReturnType<T>
function execute<T extends AnyFunc>(callback: T, args: Promise<Parameters<T>>): Promise<ReturnType<T>>
function execute<T extends AnyFunc>(callback: T, args: Parameters<T> | Promise<Parameters<T>>) {
  if (args instanceof Promise) {
    return args.then(resolved_args => {
      const result = callback(...resolved_args)
      return result
    })
  }
  return callback(...args);
}

Playground

【讨论】:

  • 非常感谢!我只是添加一个案例来做我想做的所有事情
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-02
  • 2016-08-12
  • 1970-01-01
  • 2015-12-08
相关资源
最近更新 更多