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

Convert Promise to async function #202

Open
nt1m opened this issue Dec 29, 2016 · 14 comments
Open

Convert Promise to async function #202

nt1m opened this issue Dec 29, 2016 · 14 comments

Comments

@nt1m
Copy link

nt1m commented Dec 29, 2016

Input:

var promise = function() {
  return new Promise((resolve) => function() {
    setTimeout(resolve, 200, 4);
  });
};
var aF = function() {
  return new Promise(resolve => {
    promise.then((data) => {
      resolve(data);
    });
  });
}

Expected output:

let promise = () => new Promise((resolve) => function() {
    setTimeout(resolve, 200, 4);
  });
};

let aF = async () => {
  return await promise;
};
@nt1m
Copy link
Author

nt1m commented Dec 29, 2016

Also:

let bob = {  
  asyncFunc: async function() {
  }
};

should be changed to:

let bob = {
  async asyncFunc() {}
}

@nene
Copy link
Collaborator

nene commented Dec 29, 2016

The first example looks weird. The aF function is basically pass-through - why would anybody write such code. It also doesn't actually work as the function promise is never called. My gut feeling is that implementing any Promise to async-await conversion will be pretty complicated task.

The other proposal should automatically work once we have support for parsing the async-await syntax, and that's really the part behind which all of this is waiting. It also requires Recast to support that additional syntax - it allows to swap the parser and use e.g. babylon, but I think it only really supports syntax up to ES2016.

@nene nene changed the title Convert to async function Convert Promise to async function Dec 29, 2016
@nt1m
Copy link
Author

nt1m commented Dec 30, 2016

@nene Sorry, the first example was some quick one-off code. Maybe this blog post has better examples: https://hacks.mozilla.org/2016/12/asyncawait-arrive-in-firefox/

@nt1m
Copy link
Author

nt1m commented Dec 30, 2016

instead of nested Promise callbacks, we want async/await

@tunnckoCore
Copy link

@nene Babylon supports everything :D https://github.com/tunnckoCore/parse-function/blob/master/test.js is proof that it works for async/await.

@nene
Copy link
Collaborator

nene commented Dec 30, 2016

@tunnckoCore Ah, I guess my comment was pretty ambiguous. My concern wasn't about babylon, but about Recast supporting the things babylon supports.

@hzoo
Copy link

hzoo commented Jan 2, 2017

@nene recast should support everything Babel does after my PR to update it to support Babel 6 changes; if not we can make a issue/pr

@nene
Copy link
Collaborator

nene commented Jan 2, 2017

Ah, that's great to hear. I definitely have to try it out then.

@kornelski
Copy link

Now that Node.js supports async natively, I can't wait to convert from the explicit Promise syntax.

Conversion of new Promise may not be possible, but replacements of successive .then() with await should be doable.

@rg1
Copy link

rg1 commented Oct 8, 2019

@kornelski

[...] but replacements of successive .then() with await should be doable.

I've thought so, too, and started implementing a transformation. It turns out, when chaining this becomes very hard, because the handler functions can be arbitrarily complex. So for the general case, all you can do is replace the .then call with await and then call the handler (still inline) with the result. It would look like this:

/* maybe return and/or chain */promise.then(function(result) { /* complex code */ })

becomes

let result = await promise;
/* maybe return and/or chain */(function (result) { /* complex code */ })(result);

Now usually, what is marked as /* complex code */ is actually quite simple, often just a return result which would allow us to simply remove the whole function call. My question is now: Is there code in lebab (or a separate module) that can recognize and optimize simple cases?

@nene
Copy link
Collaborator

nene commented Oct 9, 2019

Well, I can only speak for Lebab, which doesn't have anything like that.

One of the main complexities with promise to await conversion, is that promise execution is asynchronous, while await is semi-synchronous - execution of async function will pause at await.

So, when you have:

function(url) {
  fetch(url).then(doSomething);
  return true;
}

You can't simply convert it to:

async function(url) {
  doSomething(await fetch(url));
  return true;
}

I don't even know what a good conversion result would be.

@rg1
Copy link

rg1 commented Oct 10, 2019

You're right, it's only possible to convert then expressions that are last in the function and the function should return the promise, since that's what await does. Updating your example that way, we'd have

function(url) {
  return fetch(url).then(doSomething);
}

This can simply be converted into

async function(url) {
  return doSomething(await fetch(url));
}

However, that's not the case I'm looking at (and I'm not even sure I'd want to do this conversion).

What I'm looking at is when doSomething is written as an inline anonymous or arrow function like this:

function(url) {
  return fetch(url).then(result => { return { from: url, got: result}; });
}

So far, I believe if the outer function is written properly to return the Promise, the last (or only) handler function can be inlined (except for variable name collisions). So the last example can be converted to this:

async function(url) {
  let result = await fetch(url);
  return { from: url, got: result};
}

Yes, that invalidates/solves the example in my previous comment. However, once we get to chaining, I'm back to the previous solution. Consider this example:

function(url) {
  return fetch(url)
    .then(result => { return result.json(); })
    .then(result2 => { return { from: url, got: result2}; });
}

So far I've implemented a simple case that checks if the function only returns as its last statement and inlines it explicitly, assigning the return value to the next function's argument. That converts the example to this:

async function(url) {
  let result = await fetch(url);
  let result2 = await result.json();
  return { from: url, got: result2};
}

There are still a couple of questions I'm wondering about (sorry if they don't directly concern this feature):

  • are there other cases where a directly called function could be optimized away?
  • would a labeled block be better (you can break out of it, just like a return)?
  • should I write an optimization function like I've asked for instead of directly recognizing and inlining the code?

I hope to find more time to work on this and eventually release something, but I can't promise anything. In the meantime I'd be happy to hear other thoughts on this.

@nene
Copy link
Collaborator

nene commented Oct 11, 2019

This last example of yours looks pretty neat.

  • are there other cases where a directly called function could be optimized away?

Not sure I really understand what sort of optimizing-away you have in mind.

  • would a labeled block be better (you can break out of it, just like a return)?
  • should I write an optimization function like I've asked for instead of directly recognizing and inlining the code?

My goal has always been to have an output that would most resemble the code one would write himself. Also, the code produced by Lebab should be improvement over the existing code - if the result is harder to read than the original, then such a transform is not really worth having. The labeled blocks and IIFE-s look more like some compiler output - so I'd steer away from these.

As the main merit of async/await is the ability to write your code in synchronous-looking manner, I think that's the kind of output we should strive for in here.

@jedwards1211
Copy link

jedwards1211 commented Apr 16, 2020

I'm pleased to announce a new codemod I developed for this. It's still a bit of a work in progress, but it's getting pretty baller. You may be interested in integrating it into lebab. (Although the README shows how to use it with jscodeshift, the transform is actually implemented with pure babel 7).

https://github.com/codemodsquad/asyncify

Right now if you want to see examples look in the tests. I'll plan on making a try-it site in the next few days.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants