IHttpPromise incorrectly extends IPromise with Typ

2020-03-01 17:14发布

问题:

I started to use Typescript 2.5 and now I get this message for code in the Angular typescript definition file:

interface IHttpPromise<T> extends IPromise<T> {
    success(callback: IHttpPromiseCallback<T>): IHttpPromise<T>;
    error(callback: IHttpPromiseCallback<any>): IHttpPromise<T>;
    then<TResult>(successCallback: (response: IHttpPromiseCallbackArg<T>) => IPromise<TResult>, errorCallback?: (response: IHttpPromiseCallbackArg<any>) => any): IPromise<TResult>;
    then<TResult>(successCallback: (response: IHttpPromiseCallbackArg<T>) => TResult, errorCallback?: (response: IHttpPromiseCallbackArg<any>) => any): IPromise<TResult>;
}

Now getting an error message saying:

Severity    Code    Description Project File    Line    Suppression State Error TS2430  (TS) Interface 'IHttpPromise<T>' incorrectly extends interface 'IPromise<T>'.   Types of property 'then' are incompatible.
    Type '{ <TResult>(successCallback: (response: IHttpPromiseCallbackArg<T>) => IPromise<TResult>, errorCa...' is not assignable to type '{ <TResult>(successCallback: (promiseValue: T) => IHttpPromise<TResult>, errorCallback?: (reason:...'.
      Types of parameters 'successCallback' and 'successCallback' are incompatible.
        Types of parameters 'promiseValue' and 'response' are incompatible.
          Type 'IHttpPromiseCallbackArg<T>' is not assignable to type 'T'.  admin   C:\H\admin\admin\lib\typings\angularjs\angular.d.ts 1273    Active

Does anyone have any ideas what could be wrong? Would appreciate any advice you can give.

For reference here's the IPromise:

interface IPromise<T> {
    /**
     * Regardless of when the promise was or will be resolved or rejected, then calls one of the success or error callbacks asynchronously as soon as the result is available. The callbacks are called with a single argument: the result or rejection reason. Additionally, the notify callback may be called zero or more times to provide a progress indication, before the promise is resolved or rejected.
     *
     * This method returns a new promise which is resolved or rejected via the return value of the successCallback, errorCallback. It also notifies via the return value of the notifyCallback method. The promise can not be resolved or rejected from the notifyCallback method.
     */
    then<TResult>(successCallback: (promiseValue: T) => IHttpPromise<TResult>, errorCallback?: (reason: any) => any, notifyCallback?: (state: any) => any): IPromise<TResult>;
    /**
     * Regardless of when the promise was or will be resolved or rejected, then calls one of the success or error callbacks asynchronously as soon as the result is available. The callbacks are called with a single argument: the result or rejection reason. Additionally, the notify callback may be called zero or more times to provide a progress indication, before the promise is resolved or rejected.
     *
     * This method returns a new promise which is resolved or rejected via the return value of the successCallback, errorCallback. It also notifies via the return value of the notifyCallback method. The promise can not be resolved or rejected from the notifyCallback method.
     */
    then<TResult>(successCallback: (promiseValue: T) => IPromise<TResult>, errorCallback?: (reason: any) => any, notifyCallback?: (state: any) => any): IPromise<TResult>;
    /**
     * Regardless of when the promise was or will be resolved or rejected, then calls one of the success or error callbacks asynchronously as soon as the result is available. The callbacks are called with a single argument: the result or rejection reason. Additionally, the notify callback may be called zero or more times to provide a progress indication, before the promise is resolved or rejected.
     *
     * This method returns a new promise which is resolved or rejected via the return value of the successCallback, errorCallback. It also notifies via the return value of the notifyCallback method. The promise can not be resolved or rejected from the notifyCallback method.
     */
    then<TResult>(successCallback: (promiseValue: T) => TResult, errorCallback?: (reason: any) => TResult, notifyCallback?: (state: any) => any): IPromise<TResult>;

    /**
     * Shorthand for promise.then(null, errorCallback)
     */
    catch<TResult>(onRejected: (reason: any) => IHttpPromise<TResult>): IPromise<TResult>;
    /**
     * Shorthand for promise.then(null, errorCallback)
     */
    catch<TResult>(onRejected: (reason: any) => IPromise<TResult>): IPromise<TResult>;
    /**
     * Shorthand for promise.then(null, errorCallback)
     */
    catch<TResult>(onRejected: (reason: any) => TResult): IPromise<TResult>;

    /**
     * Allows you to observe either the fulfillment or rejection of a promise, but to do so without modifying the final value. This is useful to release resources or do some clean-up that needs to be done whether the promise was rejected or resolved. See the full specification for more information.
     *
     * Because finally is a reserved word in JavaScript and reserved keywords are not supported as property names by ES3, you'll need to invoke the method like promise['finally'](callback) to make your code IE8 and Android 2.x compatible.
     */
    finally<TResult>(finallyCallback: () => any): IPromise<TResult>;
}

回答1:

The bits of code you provided suggest that you've got an old version of the Angular typings file. This version does indeed incorrectly (in the eyes of Typescript 2.4+) extend IPromise<T>, and thus is not compatible with v2.4+.

Strict contravariance for callback parameters

Typescript "tightened up" the type checking for callback function parameters in 2.4.0, and made further improvements in 2.4.2. This is documented on the "What's new in Typescript (2.4)" wiki, and on the "Breaking changes" page for 2.4.

At the bottom of the compiler error stack, it does make sense that IHttpPromiseCallbackArg<T> isn't assignable to T. So the typings file you have has always been "incorrect" in this respect, but the compiler wasn't smart enough to recognize it as such until v2.4.

Illustration

The Mappable<T> example is remarkably similar to IPromise.then(). We can adapt this example by extending the interface:

interface Mappable<T> {
    map<U>(f: (x: T) => U): Mappable<U>;
}

type R = {};

interface SubMappable<T> extends Mappable<T> {
    map<U>(f: (x: R) => U): Mappable<U>;
}

This code will compile fine with Typescript 2.3.3. Typescript 2.4 will (rightfully) complain that R is not assignable to T.


As I mentioned, this example is (essentially) structurally identical to (a slimmed down version of) IPromise.then(). We can rename the functions, interfaces, parameters, and types, giving:

interface MyPromise<T> {
    then<TResult>(successCallback: (promiseValue: T) => TResult): MyPromise<TResult>;
}

type R = {};

interface MyHttpPromise<T> extends MyPromise<T> {
    then<TResult>(successCallback: (response: R) => TResult): MyPromise<TResult>;
}

Again, Typescript 2.3.3 or earlier will accept this code, while Typescript 2.4+ will not. Substituting IHttpPromiseCallbackArg<T> for R yields the same result.

The Fix

Install a newer types file.

A relatively recent typings file would have IHttpPromise<T> defined as

interface IHttpPromise<T> extends IPromise<IHttpPromiseCallbackArg<T>> {   }

or

type IHttpPromise<T> = IPromise<IHttpResponse<T>>;