Monday, 24 December 2018

TypeScript, async and Return Type

The other day when writing one TypeScript async method I got into some doubts that made me revisit the last part of this post, the one about the wrapper Promise that the compiler creates for an async method and how this won't be resolved until the internal Promise is resolved. Basically I have one async method that invokes to async methods to return a Person object.


class Person {
    constructor() {
        this.name = "";
        this.city = "";
    }
}
function getFirstId() { //returns a Promise of a string after 1 second
    return new Promise((res, rej) => {
        setTimeout(() => res("1AB2"), 1000);
    });
}
function getPerson(id) { //returns a Promise of a Person after 1 second
    return new Promise((res, rej) => {
        let p = Object.assign(new Person(), {
            name: "Francois",
            city: "Paris"
        });
        setTimeout(() => res(p), 1000);
    });
}

In JavaScript (ES2017), I know I can write the client code in 2 ways:

Doing a return await

async function getFirstPerson() {
    let id = await getFirstId();
    return await getPerson(id); //this return happens after 2 seconds
}

let per = await getFirstPerson(); //all in all we wait 2 seconds

Doing a return

async function getFirstPerson2() {
    let id = await getFirstId();
    return getPerson(id);  //this return happens after 1 second
}

let per = await getFirstPerson2(); //all in all we wait 2 seconds

The first case is pretty clear, return await getPerson(id); returns a Person, that resolves the wrapper Promise that was created and returned when the function was first invoked. The second case is what I explained in that previous post. return getPerson(id); returns a Promise of a Person, and the wrapper Promise won't be resolved until this one is resolved. So for most aspects both codes are equivalent.

The doubt for me when writing equivalent code in Typescript was with the return types. For the second function I was writing:

async function getFirstPerson2(): Promise<Person>{
    let id = await getFirstId();
    return  getPerson(id);  //returns really a sort of Promise>
}

let per = await getFirstPerson2();
    

The TypeScript compiler allows this and compiles it to the JavaScript above. The return is not returning a Person, but a Promise of a Person, with joined to the wrapper Promise created by the runtime gives us a sort of Promise of a Promise of a Person. Anyway, as the external await will wait for the resolution of the inner Promise, the TypeScript compiler is happy with the Promise function signature.

As I explain in the comments to the JavaScript code, depending on whether we do return await or return the return happens 2 or 1 second after the invokation. Anyway, the external await waits for 2 seconds since the method call is launched. There's one more difference related to this that could be important, exception handling. Let's say the Promise returned by getPerson were rejected. In the return await case, a try-catch in getFirstPerson will catch the exception, I mean:

async function getFirstPerson(fail: boolean): Promise<Person>{
    try{
        let id = await getFirstId();
        return await getPerson(id, fail);
    }
    catch{
        console.log("exception caught in getFirstPerson");
        
    }
    return new Person();
}

async function main(){
    console.log(Date.now() + " before calling getFirstPerson");
    //no need for a try-catch here, the one in getFirstPerson catches it all
    let p = await getFirstPerson(true);
    console.log(Date.now() + " person: " + JSON.stringify(p));
}

In the case of return we are just returning the Promise, so we'll need the try-catch for the outer await also, I mean:


async function getFirstPerson2(fail: boolean): Promise<Person>{
    //only catches the getFirstId rejection, but not the getPerson
    try{
        let id = await getFirstId();
        return getPerson(id, fail);
    }
    catch{
        console.log("exception caught in getFirstPerson");
    }
    return new Person();
}

async function main2(){
    console.log(Date.now() + " before calling getFirstPerson");
    //we need a try-catch here for the getPerson rejection
    try{
        let p = await getFirstPerson2(true);
        console.log(Date.now() + " person: " + JSON.stringify(p));
    }
    catch{
        console.log("exception caught in main2");
    }
}

No comments:

Post a Comment