Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
elizabethdinella opened this issue Jun 19, 2018 · 6 comments
Closed
Assignees
Labels
Committed The team has roadmapped this issue Domain: Refactorings e.g. extract to constant or function, rename symbol Suggestion An idea for TypeScript

Comments

@elizabethdinella
Copy link
Contributor

elizabethdinella commented Jun 19, 2018

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:

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:

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:

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:

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:

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:

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

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:

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

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:

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

@mhegazy mhegazy added Suggestion An idea for TypeScript API Relates to the public API for TypeScript Domain: Refactorings e.g. extract to constant or function, rename symbol and removed API Relates to the public API for TypeScript labels Jun 20, 2018
@mhegazy mhegazy added this to the TypeScript 3.0 milestone Jun 20, 2018
@gioragutt
Copy link

gioragutt commented Jun 20, 2018

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

@capaj
Copy link

capaj commented Jun 20, 2018

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 ?

@JoshuaKGoldberg
Copy link
Contributor

JoshuaKGoldberg commented Jun 21, 2018

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

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Jun 27, 2018

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 😃

@DanielRosenwasser
Copy link
Member

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.

@karlhorky
Copy link
Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Committed The team has roadmapped this issue Domain: Refactorings e.g. extract to constant or function, rename symbol Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

8 participants