babel / babel

🐠 Babel is a compiler for writing next generation JavaScript.
https://babel.dev
MIT License
43.02k stars 5.6k forks source link

Implement transform-async-to-promises #7076

Open MatAtBread opened 6 years ago

JSteunou commented 5 years ago

Have you guys looked at this plugin ? https://github.com/rpetrich/babel-plugin-transform-async-to-promises Just passing by :)

Dan503 commented 5 years ago

Have you guys looked at this plugin? https://github.com/rpetrich/babel-plugin-transform-async-to-promises

That plugin promises to turn async into promises but it does not fullfill it's promise. (😜)

It more turns it into a series of callbacks.

I prefer this one since it really does turn async calls into real promises (It can be a bit more restrictive in what it can translate though) https://www.npmjs.com/package/babel-plugin-async-to-promises

JSteunou commented 5 years ago

Indeed, a better name would have been transorm-async-to-callbacks-pyramid-hell but that would not sell :D

I saw the one by Kneden but it as this big warning on top :fearful:

Worthaboutapig commented 5 years ago

Indeed, a better name would have been transorm-async-to-callbacks-pyramid-hell but that would not sell :D

I saw the one by Kneden but it as this big warning on top

I made comment on this a while ago and took a look at improving the implementation, but haven't had time.

TBH, I'm entirely baffled by the whole regenerator-runtime choice in respect of await-async- nearly all browsers have supported Promises for years and those that don't can be polyfilled. Was regenerator-runtime written before the Promises spec came out?

dpraul commented 5 years ago

I notice this PR has been inactive for a few months now - is there hope yet for this feature? We've been using fast-async in the meantime (with transform-regenerator and transform-async-to-generator disabled), but it would be nice to know if this will have proper support in the near future.

matAtWork commented 5 years ago

Hi @dpraul - as far as I know, this is with the Babel guys to decide/approve

erykpiast commented 4 years ago

Hello @mAAdhaTTah and Babel Team (@nicolo-ribaudo, @hzoo)! Do you plan to continue work on this PR? What are the pending issues? Do you still see a value in this concept? If you need any help, I'm here for you!

MatAtBread commented 4 years ago

I have no plans to a work on it.

It was left to @hzoo and the Babel team to decide whether to incorporate it or not

nicolo-ribaudo commented 4 years ago

https://github.com/babel/babel/pull/7076#pullrequestreview-128418552 is a good summary of the minimum steps that need to be done to move this forward. However,

  1. This PR is meant to avoid depending on regenerator-runtime when using async functions. If you configure @babel/preset-env to target >0.6%, not ie 11 it will not use regenerator, and if you lower your targets (>0.5% or accept ie 11) regenerator is included for browsers that don't support promises anyway.
  2. If this doesn't get accepted into this monorepo, nothing prevents anyone from releasing it as a third-party plugin, and use it disabling transform-regenerator and async-to-generator in @babel/preset-env's options.
curiousdannii commented 4 years ago

In the time that this PR has sat open the need for it has largely disappeared. Async function support is now up to above 90%. Still, thank you @MatAtBread for trying.

ljharb commented 4 years ago

I don’t agree that the need has disappeared at all; I’d still very much like to see this land in core.

“90%” doesn’t mean anything; since everyone’s targets are different.

erykpiast commented 4 years ago

There is no need to remove the current approach when this one would be merged. It may be an option like a loose mode is for many presets, don't you think? In that way, everybody may choose what is best for a particular use case.

nicolo-ribaudo commented 4 years ago

This transform really needs to be rewritten as a Babel plugin, as regenerator-transform has been for a long time (though not originally!), so that it can benefit from all the pipelining and NodePath/Scope-reuse optimizations that Babel's plugin system provides, and so that the Babel team can meaningfully maintain it, as you are implicitly asking them to do. Right now this plugin has a bus/vacation factor of 1, and that becomes a liability for Babel when it is included in core.

https://github.com/babel/babel/pull/7076#pullrequestreview-128418552

Dan503 commented 4 years ago

What would the use case be for preferring generators over promises though? 😕

erykpiast commented 4 years ago

@nicolo-ribaudo I meant an option to the preset, similar to modules in env (as far as I understand it's a case of adding a plugin conditionally).

@Dan503 I suppose compliance with specs would be the benefit of the original generator approach. Maybe there is something more, like handling more complex cases. I'm thinking about nested and conditional awaits, async generators etc.

In general, what I would like to accomplish is not having to advise people dropping async-await syntax for simple cases, like unconditional series of asynchronous operations. I don't like multiple then callbacks like everybody else, but what I don't like even more is that whole regenerator runtime and a lot of boilerplate added to the bundle just to handle the very simple cases. I also understand the necessity of having a fallback solution for more complex code, constructs that we may even not dream about. That's why I see a space for two separate and independent solutions, possibly chosen by a preset option.

nicolo-ribaudo commented 4 years ago

I meant an option to the preset, similar to modules in env (as far as I understand it's a case of adding a plugin conditionally).

It doesn't matter if it's the default behavior or not. If it's not implemented as a Babel plugin we cannot maintain it. Nodent isn't a Babel plugin. It passes the original AST to an external compiler, which doesn't use Babel's architecture.

Deleted - I was wrong 1. When there is an async function, transform it into a source code string 2. Pass this string to the nodent compiler 3. Get a compiled source code from the node compiler, parse it and put it back in the original position

The whole compilation is opaque to us, and it's not something that we can maintain. The current implementation (as a "fake" plugin) can be published to npm, and then you can do:

{
  "presets": [
    ["@babel/preset-env", {
      "exclude": ["async-to-generator"]
    }]
  },
  "plugins": [
    "transform-async-with-nodent"
  ]
}

We have nothing against transpiling async to promises, but also the community is free to publish a plugin outside of preset-env.

I suppose compliance with specs would be the benefit of the original generator approach. Maybe there is something more, like handling more complex cases. I'm thinking about nested and conditional awaits, async generators etc.

@Dan503 This. :100:

Also, transpiling async functions to generators (not to es5) produces an output which is much more similar to the original one rather than when using promises:

// input
(async () => {
  const response = await fetch(url);
  const asJSON = await response.json();

  console.log(asJSON);
})();

// Compiled to generators
_asyncToGenerator(function* () {
  const response = yield fetch(url);
  const asJSON = yield response.json();
  console.log(asJSON);
})();

// Compiled to promises
(() => new Promise(function ($return, $error) {
    let response, asJSON;
    return fetch(url).then((function ($await_1) {
        response = $await_1;
        return response.json().then((function ($await_2) {
            asJSON = $await_2;
            console.log(asJSON);
            return $return();
        }).$asyncbind(this, $error), $error);
    }).$asyncbind(this, $error), $error);
}))();

Additionally, I just noticed that nodent's output modifiers the native Function.prototype object. It's not wrong per se, but it's something that so far we have been avoided on purpouse in Babel's generated code.

MatAtBread commented 4 years ago

Actually, that's not true. It passes the parsed AST to nodent, which does all the tree transforms itself (not using the Babel utils).

I'm all in favour of debate, but please base it on facts. It did considerable work on factoring it the transformer especially for this PR. It's up to you if you use it, but don't misrepresent what it does

nicolo-ribaudo commented 4 years ago

Thanks, I updated my comment.

nicolo-ribaudo commented 4 years ago

In the short term, we could link the third-party plugin in Babel's docs.

erykpiast commented 4 years ago

Alright, I see the whole story now. If it's about maintainability and code ownership, maybe there is nothing wrong with rejecting this PR and continuing the work somewhere else? I suppose rewriting the code to Babel utils would require starting the work from scratch. It's far more than just polishing the code.

If we're focused on results more than on resolving technical or operational issues, maybe we should start from releasing a stable version of fast-async working well with Babel@7, as a community plugin. Then we could try to measure its adaptation in the ecosystem and battle test in real-world projects. ~I'm not sure how wide is the usage of the plugin now, but NPM shows rather low numbers. Maybe refreshing the project and some marketing in social media would be enough to raise its popularity and get more feedback.~ The correct plugin is this one.

@nicolo-ribaudo, @MatAtBread, what do you think?

matAtWork commented 4 years ago

Whatever is appropriate for you.

As for maintainability, it is a complex piece of code, and quite old (the original implementation is maybe 5 years old). However, it has a full test suite in the main compiler (nodent, as opposed no nodent-transform which is just the AST transformer) which covers many more test cases than babel's current set and it has been in use in production systems for many years. I've not had to do anything to it for months to meet edge cases or indeed the spec (there are compiler flags for strict and loose adherence to the spec, along with options for output to ES5, Promises, ES6, etc).

If I remember correctly, the babel version defaults to strict adherence to the spec with no runtime at all and using Promises.

All that having been said, like last time this came up, this seems to be a "political" or "philosophical" debate. It's not about the code or it's functionality (which is fully in line with the rest of the world), it's about whether making the option available to end users of babel should be easy (included) or hard (a comment somewhere in the docs) vs. the potential cost of maintenance. This is not something I have a view on as I'm not a babel maintainer.

erykpiast commented 4 years ago

Matt, you're confusing me. Are you at work or having a sandwich break? 😐

MatAtBread commented 4 years ago

Erm, both? It was lunch time in London.

I'm not trying to be confusing. I'm genuinely agnostic about whether Babel include the transformer or not. Most of my day job is backend, or if it's FE it's not for IE11, and I'm not a Babel user in general. So it really is whether there's a demand in the Babel user base for an async transformer with no runtime.

As an aside, when I first wrote it, it really was 10x quicker than regenerator & Promise polyfills, but now these are both implemented in major engines like V8, the point is moot as native is easily twice as quick as any userland implementations

Should you decide to integrate into Babel's core product, I'm happy to advise where I can, but it's not something I have an active interest in myself

MatAtBread commented 4 years ago

I also think you're looking at the wrong plugin. Try https://www.npmjs.com/package/fast-async - that has 22k a week. I think someone built their own plugin on top of fast-sync for Babel & used the name

erykpiast commented 4 years ago

Just a stupid joke, you use two GitHub accounts interchangeably. I suppose by accident, but I found it funny because of the names. Sorry about that.

Do you refer runtime speed, right? I'm more interested in optimizing bundle size. I didn't consider potential improvement or regression in execution, but my gut feeling is Promises winning here as well, there is less code to run. On the other hand, there is some cost of creating many lambdas for then callbacks... It's definitely something necessary to check out.

Huh, I'm looking at the wrong plugin! 😱

MatAtBread commented 4 years ago

Oh yeah! I forgot. I have a paid-for-put-food-on-the-table and other stuff. It depends if I respond from my phone or desktop!

dpraul commented 4 years ago

since this is showing activity again, just chiming in once more: if you're just trying to avoid using regenerator-runtime for promises, we've been using fast-async in production for a long time now with no issue.

babel.config.js ```js module.exports = { plugins: [ [ '@babel/plugin-transform-regenerator', { asyncGenerators: false, generators: true, async: false, }, ], [ 'module:fast-async', { spec: true, }, ], ], presets: [ [ '@babel/preset-env', { exclude: [ 'transform-regenerator', 'transform-async-to-generator', ], }, ], ], }; ```
matAtWork commented 4 years ago

@erykpiast - if you want the Babel-7 plugin, that I prepped for this PR, it's here: https://github.com/MatAtBread/fast-async/tree/babel-v7

matAtWork commented 4 years ago

@nicolo-ribaudo The comment about modifying the Function.prototype is a fair one, but you have not used the super-clean & spec options to test. Here's is the output with no runtime at all (function protoypical or otherwise).

Of course, it's even bigger and more ugly, but somewhat easier to read that regenerators output. Of course, if you have a runtime that does generators, you don't need either.

http://nodent.mailed.me.uk/#(async%20()%20%3D%3E%20%7B%0A%20%20const%20response%20%3D%20await%20fetch(url)%3B%0A%20%20const%20asJSON%20%3D%20await%20response.json()%3B%0A%20%20%0A%20%20console.log(asJSON)%3B%0A%7D)()%3B%0A~options~%7B%22mode%22%3A%22promises%22%2C%22promiseType%22%3A%22Zousan%22%2C%22noRuntime%22%3Atrue%2C%22es6target%22%3Atrue%2C%22wrapAwait%22%3Atrue%2C%22spec%22%3Afalse%7D

coolgod commented 4 years ago

What's the latest story about this PR? Seems it has been open for a long time.

nicolo-ribaudo commented 4 years ago

You can use the async-to-promises plugin as shown in the config example at https://github.com/babel/babel/pull/7076#issuecomment-593412895