ค้นหาเว็บไซต์

TypeScript - สร้างประเภทแมปเพื่อตัวแปรค่ารีเทิร์นให้เป็น Promise


สมมติว่าฉันกำลังจัดการกับวัตถุที่สอดคล้องกับอินเทอร์เฟซดังต่อไปนี้:

interface Foo {
    getCount(): number;
    doSomething(): boolean;
}

มันมีเพียงฟังก์ชันเท่านั้น และไม่มีฟังก์ชันใดเป็นแบบอะซิงค์ อย่างไรก็ตาม ฉันไม่ได้เข้าถึงอ็อบเจ็กต์แบบซิงโครนัสเสมอไป และในบางกรณีจะต้องจัดการกับเวอร์ชันอะซิงโครนัส ซึ่งค่าที่คืนจากฟังก์ชันทั้งหมดถูกห่อหุ้มใน Promises ดังนี้:

interface AsyncFoo {
    getCount(): Promise<number>;
    doSomething(): Promise<boolean>;
}

ฉันกำลังพยายามสร้างชนิดแบบ mapped ของ Typescript เพื่อแทนการเปลี่ยนรูปแบบแบบนี้ เนื่องจากฉันมีอินเทอร์เฟซของอ็อบเจ็กต์จำนวนมากและไม่ต้องการคัดลอกแต่ละอินเทอร์เฟซเพียงอย่างเดียวจนสุดท้ายมีทั้ง interface [name] และ interface Async[name] และทุกโปรโตไทป์ของเมธอดถูกคัดลอกซ้ำ

ความคิดแรกของฉันคือบางทีฉันอาจจะปรับเปลี่ยนอินเทอร์เฟซได้ดังนี้:

type Self<T> = T;
interface Foo<S = Self> {
    getCount(): S<number>;
    doSomething(): S<boolean;
}
type AsyncFoo = Foo<Promise>;

แต่ทั้ง Self และ Promise ต้องการเจนเนอริกส์ให้กำหนดแบบสถิติตอนที่ฉันใช้พวกมัน แทนที่จะสามารถใช้พวกมันในลักษณะย้อนกลับแบบนี้ได้

ดังนั้นต่อไปฉันพยายามสร้างชนิดแผนที่บางประเภทเช่น:

type Promisify<T> = {[K in keyof T]: Promise<T[K]>}

แต่แน่นอนว่านี่จะครอบคลุมทุกวิธีของอินเทอร์เฟซใน Promise แทนที่จะเป็นแค่ค่าที่ส่งกลับ ทำให้ฉันได้:

type PromisifiedFoo = {
    getCount: Promise<() => number>;
    doSomething: Promise<() => boolean>;
}

ฉันได้พยายามขยายความในเรื่องนี้โดยการใช้การกำหนดขอบเขตกับชนิดทั่วไป T ของ Promisify เช่น:

type Promisify<T extends {[key: string]: <S>() => S}> = ...

แต่ฉันดูเหมือนจะไม่สามารถทำให้ทุกอย่างเข้ากันได้

และตอนนี้ฉันอยู่ที่นี่ มีวิธีใดบ้างที่ฉันสามารถสร้าง type (แบบ mapped หรือแบบอื่น) ที่แสดงถึงการแปลง “Promisify” นี้ไปยังค่าที่ส่งคืนของ type ได้หรือไม่?

คำตอบ #1

ด้วย Conditional Types ใหม่ใน Typescript 2.8 คุณสามารถทำสิ่งต่อไปนี้ได้:

// Generic Function definition
type AnyFunction = (...args: any[]) => any;
// Extracts the type if wrapped by a Promise
type Unpacked<T> = T extends Promise<infer U> ? U : T;

type PromisifiedFunction<T extends AnyFunction> =
    T extends () => infer U ? () => Promise<Unpacked<U>> :
    T extends (a1: infer A1) => infer U ? (a1: A1) => Promise<Unpacked<U>> :
    T extends (a1: infer A1, a2: infer A2) => infer U ? (a1: A1, a2: A2) => Promise<Unpacked<U>> :
    T extends (a1: infer A1, a2: infer A2, a3: infer A3) => infer U ? (a1: A1, a2: A2, a3: A3) => Promise<Unpacked<U>> :
    // ...
    T extends (...args: any[]) => infer U ? (...args: any[]) => Promise<Unpacked<U>> : T;

type Promisified<T> = {
    [K in keyof T]: T[K] extends AnyFunction ? PromisifiedFunction<T[K]> : never
}

ตัวอย่าง:

interface HelloService {
    /**
    * Greets the given name
    * @param name 
    */
    greet(name: string): string;
}

function createRemoteService<T>(): Promisified<T> { /*...*/ }

const hello = createRemoteService<HelloService>();
// typeof hello = Promisified<HelloService>
hello.greet("world").then(str => { /*...*/ })
// typeof hello.greet = (a1: string) => Promise<string>

คำตอบ #2

วิธีหนึ่งในการจัดการกับสิ่งนี้คือการสร้างเวอร์ชันทั่วไปของ Foo ที่สามารถปรับแต่งให้เป็นเวอร์ชันซิงโครนัสหรือเวอร์ชันอะซิงโครนัสได้

type MaybePromise<T, B extends 'plain' | 'promise'> = { 
  plain: T, 
  promise: Promise<T> 
}[B]

ชนิด MaybePromise<T,'plain'> เท่ากับ T และ MaybePromise<T, 'promise'> เท่ากับ Promise<T> ตอนนี้คุณสามารถอธิบาย generic Foo:

type GenericFoo<B extends 'plain' | 'promise'> = {
  getCount(): MaybePromise<number, B>;
  doSomething(): MaybePromise<boolean, B>;
}

และในที่สุดก็เชี่ยวชาญ:

interface Foo extends GenericFoo<'plain'> { }
interface AsyncFoo extends GenericFoo<'promise'> { }

สิ่งนี้ควรทำงานตามที่คุณคาดไว้:

declare const f: Foo;
if (f.doSomething()) {
  console.log(2 + f.getCount());
}

declare const aF: AsyncFoo; 
aF.doSomething().then(b => {
  if (b) {
    aF.getCount().then(n => {
      console.log(2 + n);
    })
  }
});

หวังว่านั่นจะช่วยได้นะ โชคดี!

บทความที่เกี่ยวข้อง