【问题标题】:How to type Axios.all when the each promise has a different return type当每个 promise 具有不同的返回类型时如何键入 Axios.all
【发布时间】:2019-02-23 08:22:20
【问题描述】:

我有两个 Axios get 请求,它们的返回类型已明确声明。这是一个简化的示例:

interface O1 {
   prop1: string;
   prop2: boolean;
}

interface O2 {
   prop3: number;
   prop4: string;
}

interface Request<T> {
   message: string;
   data: T
}

使用这些接口,我输入了我的请求,结果对象也被输入了:

const getO1 = () => Axios.get<Request<O1>>("/API/O1");
const getO2 = () => Axios.get<Request<O2>>("/API/O2");

现在我需要并行进行此调用,因此我尝试了 Axiox.all 但 TypeScript 抱怨 O1O2 类型不匹配。具体来说,我试过了:

const getAll = () => Axios.all([getO1(), getO2()]);

据我所知,TypeScript 期望在all 的情况下,所有结果都将具有相同的类型(结构)。有没有办法进行打字并发呼叫? (使用 Axios v0.18 和 TypeScript v3.0.3)

【问题讨论】:

    标签: typescript axios typescript-typings


    【解决方案1】:

    很遗憾,Axios.all 的声明不是很好。它的输入方式不能传入元组。

    export interface AxiosStatic extends AxiosInstance {
      all<T>(values: (T | Promise<T>)[]): Promise<T[]>;
    }
    

    参数必须是单一类型的数组T。 Typescript 不会推断 T 的联合,也无法推断出预期数组的元组。

    我们可以看看Promise.all 是如何输入的(它确实支持元组)

    all<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>, T10 | PromiseLike<T10>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>;
    // ...
    all<T1, T2, T3, T4>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>]): Promise<[T1, T2, T3, T4]>;
    all<T1, T2, T3>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>]): Promise<[T1, T2, T3]>;
    all<T1, T2>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>]): Promise<[T1, T2]>;
    all<T>(values: (T | PromiseLike<T>)[]): Promise<T[]>;
    

    我们可以看到他们使用了大量的重载来支持元组。

    如果 Axios 承诺符合规范,您可以尝试使用 Promise.all。这些类型可以解决,但我自己没有测试过运行时行为。

    const getAll = () => Promise.all([getO1(), getO2()]) //() => Promise<[AxiosResponse<Request<O1>>, AxiosResponse<Request<O2>>]>
    

    或者你可以坚持使用 axios 版本并使用一些类型断言:

    const getAll = () => Axios.all<{}>([getO1(), getO2()]) as unknown as AxiosPromise<[Request<O1>, Request<O2>]>
    

    只是一个有趣的说明,在 3.1 中,我们将能够在没有 10 个重载的情况下键入 all

    type PromiseResult<T> = T extends AxiosPromise<infer R>? R: T
    type ArrayPromiseResult<T extends (any | AxiosPromise<any>)[]> = { [P in keyof T]: PromiseResult<T[P]> }
    declare function all<T extends [] | (any | AxiosPromise<any>)[]>(values: T): AxiosPromise<ArrayPromiseResult<T>>
    
    let result = all([getO1(),getO2()]) // result is AxiosPromise<[Request<O1>, Request<O2>]>
    

    【讨论】:

    • 使用Promise.all 有效,但是我有从响应中获取数据的开销(Axios 会自动执行)。我可能还需要处理错误。它需要进一步研究......不过,它是一个有效的解决方案。也可以考虑使用类型断言as unknown as AxiosPromise&lt;[Request&lt;O1&gt;, Request&lt;O2&gt;]&gt;。虽然我不太喜欢强制转换(我本可以直接在响应中进行强制转换),但这个解决方案会将强制转换与函数放在同一位置,因此在更改内部承诺时更有可能被发现all.
    【解决方案2】:

    您可以创建一个基本接口并让您的 O1 和 O2 接口扩展此接口。在 Axios.all 语句中使用基接口作为泛型类型参数。

      import Axios from 'axios';
    
        //base interface
        interface axiosResponse{}
    
        //extend the base interface in both interfaces
        interface O1 extends axiosResponse {
            prop1: string;
            prop2: boolean;
        }
    
        interface O2 extends axiosResponse {
            prop3: number;
            prop4: string;
        }
    
        interface Request<T> {
            message: string;
            data: T
        }
    
        const getO1 = () => Axios.get<Request<O1>>("/API/O1");
        const getO2 = () => Axios.get<Request<O2>>("/API/O2");
    
        //specify the base interface type as the generic type parameter
        const getAll = () => Axios.all<axiosResponse>([getO1(), getO2()]).then( r => {
          const result: O1 = <O1>r[0];
          const result2: O2 = <O2>r[1];
        })
    

    【讨论】:

    • 结果输入不正确。在then 中,您将获得axiosResponse 的数组,但我需要的是一个数组,其中第一个元素的类型为O1,第二个元素的类型为O2
    • 您可以将结果转换为所需的子类型(Anwser 编辑)。
    • 这不是很有趣。当然,我可以转换结果,但是当我有更多请求并且它们的顺序发生变化时会发生什么? TypeScript 将无法判断我正在访问不同的对象(类型)。尽管强制转换有效,但它并不能真正扩展,并且会增加在多个位置手动进行更改的开销。
    • 是的。结果看起来不是真正可扩展的。目前我没有不同的方法 - 抱歉。
    • 感谢您的帮助!
    猜你喜欢
    • 2019-08-20
    • 1970-01-01
    • 2021-08-07
    • 1970-01-01
    • 1970-01-01
    • 2021-02-06
    • 1970-01-01
    • 2019-07-01
    • 2019-05-04
    相关资源
    最近更新 更多