2
votes

I want to refactor a promise chain into async/await, but Typescript is complaining about the typing.

TS2322:Type 'IHttpPromiseCallbackArg< IResp >' is not assignable to type 'IResp'...

I thought await would return a regular value, not a promise. Am I wrong? If so, how can I assign a typing so that the desired code will compile?

I thought await would return the same value as the first argument in the .then callback. Am I wrong?

Old code:

handleSubmit(form) {
    const params = this.getParams(form);

    this.myAsyncRequest(params)
       .then((resp:IResp) => this.processResp(resp))
       .catch((e:IServerError) => this.handleError(e));
 }

Desired new code:

async handleSubmit(form) {
    const params = this.getParams(form);

    try {
        const resp:IResp = await this.myAsyncRequest(params); //typing error with "const resp:IResp"
        this.processResp(resp);
    } catch (e:IServerError) {
        this.handleError(e);
    }
}

The desired code still breaks if I remove the return type in myAsyncRequest ; I guess Typescript infers directly from the AngularJS library.

myAsyncRequest(params:IParams):IHttpPromise<IResp> {
    return $http.post('blah', data);
}

If I remove "IResp" from the const resp declaration, processResponse complains that IHttp< IResp> does not equal IResp...

processResp(resp:IResp) {
    //do stuff
}
2
The type IHttpPromiseCallbackArg is not a promise. This is already the actual result of the function call. See definitelytyped.org/docs/angular-translate--angular-translate/…NineBerry
I can reword my question to - "I thought await would return the same value as the first argument in the .then callback. Am I wrong?"user2954463
You are correct. But you are wrong about what the type of the first parameter in the then callback function is. See definitelytyped.org/docs/angularjs--angular-route/interfaces/…NineBerry
Maybe you are using the wrong type definition for what you are actually using. But we need a full working example to say that definitely.NineBerry

2 Answers

4
votes

Your question "I thought await would return the same value as the first argument in the .then callback. Am I wrong?".

No, you are absolutely right. But you are wrong about what the first argument in the .then callback is.

You define myAsyncRequest to return IHttpPromise<IResp>. But IHttpPromise<T> is defined as inheriting IPromise the following way:

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

So, an IHttpPromise<T> is a promise that returns an IHttpPromiseCallbackArg<T> back where the actual data of type T is in the data property of the IHttpPromiseCallbackArg<T>.

So, the old code variant we see in the question:

handleSubmit(form) {
    const params = this.getParams(form);

    this.myAsyncRequest(params)
       .then((resp:IResp) => this.processResp(resp))
       .catch((e:IServerError) => this.handleError(e));
 }

should actually not compile without errors in TypeScript when myAsyncRequest is defined to return IHttpPromise.

Howto fix this:

async handleSubmit(form) {
    const params = this.getParams(form);

    try {
        const httpResp:IHttpPromiseCallbackArg<IResp> = await this.myAsyncRequest(params); 
        const resp: IResp = httpResp.data;
        this.processResp(resp);
    } catch (e:IServerError) {
        this.handleError(e);
    }
}

Note: In the latest type definitions for angular, the type IHttpPromiseCallbackArg<T> is actually called IHttpResponse<T>.

Maybe in your code, you have defined IResp as IHttpResponse<Something>? Then you just have a conflict with the old name IHttpPromiseCallbackArg. Then get the newest type definitions from DefinitelyTyped that uses the new name. And you would also have to change the definition of myAsyncRequest to:

myAsyncRequest(params:IParams):IHttpPromise<Something> {
0
votes

The line containing the await does indeed await the resolved value - but because the function itself is async (to allow all that awaiting), you get back a promise.

Example... in the below code you can use x as a plain number (even though delay returns a promise) and again for y - so all the stuff you await is resolved so you can use it more like you would if it were synchronous.

The async function that doesn't look like it returns a promise, now does.

This can seem confusing, because it seems to "invert the promises", but what it does is shift the then to the top level (you could have async functions calling down to other async functions and so on).

function delay(ms: number) {
    return new Promise<number>(function(resolve) {
        setTimeout(() => {
            resolve(5);
        }, ms);
    });
}


async function asyncAwait() {
    let x = await delay(1000);
    console.log(x);

    let y = await delay(1000);
    console.log(y);

    return 'Done';
}

asyncAwait().then((result) => console.log(result));