Typescript 使用适当的参数类型承诺所有满意的功能

Typescript promisfy all satisfied functions with proper param type

我正在开发一个包含大量异步 api 的代码库,在它们的参数中涉及 success 选项,例如

declare function foo(_: { 
  success?: (_: string) => void, 
  fail?: () => void, 
}): void

declare function bar(_: { 
  success?: (_: string) => void, 
  fail?: () => void, 
  src?: string
}): void

declare function baz(_: {
 success?: (_: string) => void, 
 fail?: () => void, 
 expired: number 
}): void

declare function foobar(_: { 
  success?: (_: string) => void, 
  fail?: () => void, 
  token: string, 
  state: boolean 
}): void

我想用下面的代码承诺所有这些

interface Cont<R> {
  fail?: () => void
  success?: (_: R) => void
}

interface Suspendable<O> {
  (option: O): void
}

function suspend<R, O extends Cont<R>>(fn: Suspendable<O>) { 
  return async (opt: Omit<Omit<O, "success">, "fail">) => await new Promise<R>((resolve, _) => fn({
    ...opt,
    success: it => resolve(it),
    fail: ()=> resolve(undefined)
  }  as O )) // if any chance, I'd like to omit the `as O` but forgive it for now
}


(async () => {
  let _foo = await suspend(foo)({}) // good 
  let _bar = await suspend(bar)({}) // good
  let _baz = await suspend(baz)/* compile error here */ ({ expired: 100})
})()

我是否错过了打字稿中的一些好东西来帮助我捕获 fn 参数中 O 的真实类型,以便我可以很好地约束参数并传递编译器错误?

我想到了两种方法去这里。第一个是放弃拥有两个类型参数,只推断 O 并从中计算 R

function suspend<O extends Cont<any>>(fn: Suspendable<O>) {
  return async (opt: Omit<O, "success" | "fail">) =>
    await new Promise<Parameters<Exclude<O["success"], undefined>>[0]>(
      (resolve, _) => fn({
        ...opt,
        success: it => resolve(it),
        fail: () => resolve(undefined)
      } as O));
}

R 的计算看起来很不幸 Parameters<Exclude<O["success"], undefined>>[0]。现在以下编译没有错误:

(async () => {
  let _foo = await suspend(foo)({})
  let _bar = await suspend(bar)({})
  let _baz = await suspend(baz)({ expired: 2 })
})()

如果您不断言 as O,您得到的关于 O 的错误是一个好消息; fn() 的参数可能具有 successfailCont<any>:

中指定的更窄的属性
declare function hmm(opt: {
  success?: (_: unknown) => void,
  fail?: () => string // narrower than specified in Cont<any>
}): void;
suspend(hmm);

并且由于您调用 fn() 时使用的 arg 具有手动 successfail 方法,因此您不能保证它的行为与 fn() 相同期望。所以我认为你需要断言并继续前进。


另一种方法是保留两个类型参数,并依靠 successfail 是可选方法这一事实来使其工作:

function suspend2<O, R>(fn: Suspendable<O & Cont<R>>) {
  return async (opt: O) =>
    await new Promise<R>(
      (resolve, _) => fn({
        ...opt,
        success: (it: R) => resolve(it),
        fail: () => resolve(undefined)
      }));
}

Here we are inferring both `O` and `R` from an argument `fn` of type `Suspendable<O & Cont<R>>`.  Ideally this would automatically yield the right `R` as well as an `O` object type that doesn't include `success` or `fail` methods.  But what happens is that you'll get the right `R` (yay) but the value of `O` will be the full object type including the `success` and `fail` methods (boo).

至少现在你不需要像 as O 这样的类型断言,因为你传递的是 O & Cont<R>.

类型的值

以下仍然可以正常工作:

(async () => {
  let _foo = await suspend2(foo)({})
  let _bar = await suspend2(bar)({})
  let _baz = await suspend2(baz)({ expired: 2 })
})()

但这只是因为 O 不需要 failsuccess 属性。


无论如何,希望其中之一对您有所帮助;祝你好运!

Playground Link to code