user2954463
user2954463

Reputation: 2411

Why does Typescript think async/await returns a value wrapped in a promise?

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
}

Upvotes: 2

Views: 4223

Answers (2)

NineBerry
NineBerry

Reputation: 28499

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> {

Upvotes: 4

Fenton
Fenton

Reputation: 251152

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));

Upvotes: 0

Related Questions