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