Typescript: Proposal: Code Fix -- Convert Promise Handlers to Async and Await

Created on 20 Jun 2018  ·  6Comments  ·  Source: microsoft/TypeScript

This proposal allows the Typescript language service to automatically refactor code that uses Promise methods such as .then() or .catch() to instead use the async and await keywords.

Async/await offers many advantages over promise methods including cleaner syntax, error handling, debugging, and readability. The benefits of the synchronous style code provided by async/await are largely recognized by the Javascript community. However, there is still a substantial amount of asynchronous code written using Promises. This proposal will provide a quick and simple transition to using the async/await keywords.

promisetoasync2

Simple Examples

Input:

function ex1(): Promise<boolean> {
    return fetch('https://microsoft.com').then( result => result.ok; );
}

Output:

async function ex1(): Promise<boolean> {
    let result = await fetch('https://microsoft.com');
    return result.ok; 
}

````
---

**onRejected handlers:**

**Input:**
```ts
function ex2(): Promise<void> {
    return fetch('https://microsoft.com').then( result => console.log(result), rej => console.log("error", rej) );
}

Output:

async function ex2(): Promise<void> {
    let result;
    try {
        result = await fetch('https://microsoft.com');
    }
    catch (rej) {
        return console.log("error:", rej);
    }
    return result.ok;
}

Note that this conversion does not preserve semantics. In order to create clean, readable code, the semantics of some code is slightly altered.


Catch handlers:
Input:
ts function ex3():Promise<void> { return fetch('https://microsoft.com').then(result => console.log(result)).catch(err => console.log(err)); }
Output:

async function ex3():Promise<void> {
    let result;
    try {
        result = await fetch('https://microsoft.com');
        await console.log(result);
    }
    catch (err) {
        await console.log(err);
    }
}
````
---
## More Examples - multiple handlers

**Multiple .then() calls:**
In situations where variable names intersect, a new variable name will be chosen for intermediate variables. For example:

**Input:**
````ts
function ex4():Promise<boolean> {
    return fetch('https://microsoft.com').then(res).then(res2);
}
function res(result){
    return result.ok;
}

function res2(result){
   console.log(result);
}
````
**Output:**
````ts
async function ex4():Promise<boolean> {
    let result = await fetch('https://microsoft.com');
    let temp = await res(result);
    return res2(temp);
}

function res(result){
    return result.ok;
}

function res2(result){
    console.log(result);
}
````

**Multiple .catch() calls**
**Input:**
````ts
function ex5(): Promise<void> {
    return fetch('https://microsoft.com').then(res => console.log(res)).catch(err => console.log("err")).catch(err2 => console.log("err2", err2));
}
````
**Output:**
````ts
async function ex5(): Promise<void> {
    try {
        let res;
        try {
            res = await fetch("https://microsoft.com");
            return console.log(res);
        }
        catch (err) {
            return console.log("err");
        }
    }
    catch (err2) {
        return console.log("err2");
    }
}
````

## More Examples - Promise.all() and Promise.race()

In order to preserve semantics, code that uses `Promise.all()` cannot be entirely refactored. 
**Input**
```ts
function ex6():Promise<void> {
    return Promise.all([fetch('https://microsoft.com'), fetch('https://microsoft.com'), fetch('https://youtube.com')]).then(
    function(vals){
        vals.forEach(console.log); 
    });
}

Output:
ts async function ex6():Promise<void> { let vals = await Promise.all([fetch('https://microsoft.com'), fetch('https://microsoft.com'), fetch('https://youtube.com')]); return vals.forEach(console.log); }
Similarly, code that uses 'Promise.race()' is partially refactored:
Input
ts function ex7():Promise<void> { return Promise.race([fetch('https://microsoft.com'), fetch('https://microsoft.com'), fetch('https://youtube.com')]).then(val => console.log(val)); }
Output:
ts async function ex7():Promise<void> { let val = await Promise.race([fetch('https://microsoft.com'), fetch('https://microsoft.com'), fetch('https://youtube.com')]); return console.log(val); }

More Examples - Returning variables of Promise type

In order to preserve semantics, a refactoring will only be offered for functions that directly return a promise with callbacks.

For example, a refactoring will not be offered for the following:

function ex8() {
    let blob = fetch("https://typescriptlang.org").then(resp => console.log(resp));
    return blob;
}

However, a refactoring will be offered for the following function as it can be converted to use async and await while preserving semantics:

function ex9() {
    return fetch("https://typescriptlang.org").then(resp => console.log(resp));
}

Support for .finally() will be coming later

Committed Refactorings Suggestion

Most helpful comment

I think that the promises should be const instead of let by default.

Also, the output in the Promise.all example should not return, as the forEach is not returned in the original function

All 6 comments

I think that the promises should be const instead of let by default.

Also, the output in the Promise.all example should not return, as the forEach is not returned in the original function

I wanted to make this as VSCode extension for quite some time. Landing this in typescript of course makes much more sense, because this way any IDE integrating the language server can offer this refactor very easily. Awesome stuff-is there a code somewhere which does the refactor itself @elizabethdinella ?

How will this deal with things that are like Promises but not quite?

  • Implementations of PromiseLike?

    • jQuery 2 and/or jQuery 3 Promises?

    • Bluebird & similar?

  • Classes that extend Promise?
  • If you don't include Promise in your tsconfig lib, but do have a class named Promise?
  • If you do include Promise in your tsconfig lib, and have a class that also happens to be named Promise?

Edit: oh, and is it out of scope to suggest this fix?

const sample = async () => {
    if (condition) {
        // Just return;
        return Promise.resolve();
    }
};

So for one of your catch handler example, you have

async function foo():Promise<void> {
    let result;
    try {
        result = await fetch('https://microsoft.com');
        await console.log(result);
    }
    catch (err) {
        await console.log(err);
    }
}

Ideally result would be declared inside of the try block. It would be better as

 async function foo():Promise<void> {
-    let result;
     try {
-        result = await fetch('https://microsoft.com');
+        let result = await fetch('https://microsoft.com');
         await console.log(result);
     }
     catch (err) {
         await console.log(err);
     }
 }

Can you give specific labels to examples? It'd make it easier to reference 😃

For similar reasons, this is also problematic:

async function foo(): Promise<void> {
    let result;
    try {
        result = await fetch('https://microsoft.com');
    }
    catch (rej) {
        return console.log("error:", rej);
    }
    return result.ok;
}

Here, you will actually introduce a failure if a rejection occurred, since result will be undefined. Ideally all of your test cases ensure that no errors have been introduced under --strict mode.

result will be undefined

@DanielRosenwasser How would result being undefined be a problem here? Wouldn't the function already return on the line after the catch? As far as I can see it wouldn't ever reach the last line where result.ok is referenced unless the fetch succeeded...

Was this page helpful?
0 / 5 - 0 ratings

Related issues

blendsdk picture blendsdk  ·  3Comments

fwanicka picture fwanicka  ·  3Comments

Antony-Jones picture Antony-Jones  ·  3Comments

kyasbal-1994 picture kyasbal-1994  ·  3Comments

zhuravlikjb picture zhuravlikjb  ·  3Comments