Code Monkey home page Code Monkey logo

kneden's Introduction

Kneden (babel-plugin-async-to-promises)

This project is currently unmaintained. If you want to take over, feel free to fork the repo. If such a fork gets maintained or contains useful improvements, I'd be willing to merge back and give repo+npm access.

Build Status Dependency Status devDependency Status

Transpile ES7 async/await to vanilla ES6 Promise chains

WARNING: Kneden is usable, but it's also not complete yet.

Do you want an ES7 async/await transpiling Babel plugin, that:

  • produces readable code - even when generator functions are not available?
  • doesn't come with a runtime your users have to download?

Then look no further! Kneden (babel-plugin-async-to-promises) can help you.

Example

In

async function test() {
  await db.destroy();
}

Out

function test() {
  return Promise.resolve().then(function () {
    return db.destroy();
  }).then(function () {});
}

(The last .then() might seem superfluous at first, but the first function doesn't actually resolve to anything so it's necessary to make a valid translation.)

Kneden tries to translate ES7 async/await to promises in a manner similar to how a human would do so. Loops are converted to recursive functions, and your code is modified in such a way that a return won't just drop you in the next part of the promise chain, but actually does what you expect it to do.

For more examples, see the test/fixtures directory for both the input and output Kneden takes/produces.

Installation

$ npm install babel-plugin-async-to-promises

Usage

Note: Kneden only supports transpiling ES5 with the addition of async/await. If you're using other ES6 features (like arrow functions, let/const, classes, etc.), make sure you transpile them down to valid ES5 code first using the babel es2015 preset. See #19 for more information.

Via .babelrc (Recommended)

.babelrc

{
  "plugins": ["async-to-promises"]
}

Via CLI

$ babel --plugins async-to-promises script.js

Via Node API

require("babel-core").transform("code", {
  plugins: ["async-to-promises"]
});

You can also use the plug-in in Browserify using babelify, in Rollup by using it in conjunction with rollup-plugin-babel, and in Webpack using babel-loader.

Unsupported

  • Return statements aren't properly supported in switch and try/catch/finally statements yet (#13)
  • No eval(); but that's true for other Babel plugins/presets as well.

Contributing

There are a couple of ways to contribute, for example by:

  • Reporting test results with your code base
  • Fixing bugs, for a nice starting task see the ones labeled 'good first bug'.

Contributions are very welcome! Just open an issue or PR.

What's up with the name?

It's Dutch for 'to knead'/'to mold' - the program molds ES7 async/await constructs into promises. It seemed applicable. Pronounciation.

The npm package name is a more descriptive one as explained in issue #22.

License

ISC


Kneden is a project by Marten de Vries.

kneden's People

Contributors

abdulhannanali avatar aprilarcus avatar greenkeeperio-bot avatar marten-de-vries avatar waldyrious avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

kneden's Issues

Why does awaiting resolve a new Promise?

Currently,

async function test() {
  await db.destroy();
}

transpiles to

function test() {
  return Promise.resolve().then(function () {
    return db.destroy();
  }).then(function () {});
}

instead of

function test() {
  return db.destroy().then(function () {});
}

Why is this? We know the expression to the right of await must be a Promise, so this seems overcautious.

Doesn't allow to get a final result of an async function

Hello @marten-de-vries I have some experiments with your plugin and I found it interesting, but I have some cases when I don't get that I expected. For example I should to get sum of 100, 200, 300 but I cannot to get it.

const delay = (time, value) => new Promise((resolver, rejecter) =>
    setTimeout(() => resolver(value), time)
)

const f = async (x, y, z) => {
    const a = await delay(1000, x);
    console.log(a);
    const b = await delay(2000, y);
    console.log(b);
    const c = await delay(3000, z);
    console.log(c);


    return a + b + c;
}

f(100, 200, 300).then(sum => console.log(`Sum of two arguments is ${sum}`));

Support removing items while iterating for ForInStatement

This is probably not really worth it, but JavaScript has a for in statement to iterate through object keys, and it is a part of a language.

I think that to support this statement, you first need to obtain all keys in an object (or use ES6 generators to do it, I guess), all at once. During iteration of those objects, you need to check if a key is still in object as ECMAScript requires deleted keys to not be iterated. Adding keys during iteration is allowed in ES6, however whether it's iterated or not is implementation defined, and it's faster to ignore new keys.

Return not working.

async ({ api }, user)=> {
    const userId = _.get(user, "_id");
    if (!userId) return; // This line is the issue.
    const res = await api(`user/${userId}`);
    return await res.json();
}

Becomes:

function (_ref, user) {
    var api, userId, res, data;
    return Promise.resolve().then(function () {
        api = _ref.api;
        userId = _.get(user, "_id");

        if (!!userId) {
            return Promise.resolve().then(function () {
                return api("user/" + userId);
            }).then(function (_resp) {
                res = _resp;
                return res.json();
            }).then(function (_resp) {
                data = _resp;

                return data;
            });
        }
    }).then(function () {});
}

This is ignoring the return value.

Return not handled properly?

I have tried something out, and appearently a return value is not passed correctly.

Code:

async function someFunction() {
  return 1;
}

class Foo {
  async init() {
    try {
      var a = await someFunction();
      var b = await someFunction();
      var c = a+b;
      console.log("Math:" + (a+b))
      return c;
    } catch(e) {
      throw e;
    }
  }
}

(async ()=>{
  var e = new Foo()
  var f = await e.init()
  console.log("Res: "+f)
  return 0;
})();

Result:

"use strict";

function someFunction() {
  return Promise.resolve().then(function () {
    return 1;
  });
}

class Foo {
  init() {
    var a, b, c;
    return Promise.resolve().then(function () {
      return Promise.resolve().then(function () {
        return someFunction();
      }).then(function (_resp) {
        a = _resp;
        return someFunction();
      }).then(function (_resp) {
        b = _resp;
        c = a + b;
        console.log("Math:" + (a + b));
        return c;
      }).catch(function (e) {
        throw e;
      });
    }).then(function () {});
  }

}

(() => {
  var e, f;
  return Promise.resolve().then(function () {
    e = new Foo();
    return e.init();
  }).then(function (_resp) {
    f = _resp;
    console.log("Res: " + f);
    return 0;
  });
})();

What that outputs:

Math:2
Res: undefined

Expected:

Math:2
Res: 2 <--

Did I do something wrong in the code? I am not sure about it, so I thought I'd post it here.

Unnecessary extra tick

var foo = async () => {
  console.log(1);
  return (await 1) + 2;
}
foo();
console.log(2);

should transpile to:

var foo = function foo() {
  return new Promise(function (resolve) {
    console.log(1);
    resolve(1);
  }).then(function (_resp) {
    return _resp + 2;
  });
};
foo();
console.log(2);

which should log 1, then 2.

but instead transpiles to:

var foo = function foo() {
  return Promise.resolve().then(function () {
    console.log(1);
    return 1;
  }).then(function (_resp) {
    return _resp + 2;
  });
};
foo();
console.log(2);

This difference means the async function takes an extra tick - which results in logging 2, then 1.

Conform to Babel plugin naming scheme

Hiya, since this is just a Babel Plugin it should be following the babel plugin naming scheme.

babel-plugin-descriptive-name-of-what-it-does

I'm sure you spent a lot of time thinking of kneden, and it's nice but it'd be better to respect community standards.

Bug with variable order?

Here's a quick test, consider the following code:

function getDelayedNull() {
  // Wait 250ms to retry
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(null)
    }, 1000)
  })
}
async function test() {
  const a = Math.PI
  const n = await getDelayedNull()
  console.log(`${a}`)
}
test()

Which transpiles to:

function test() {
  return Promise.resolve().then(function () {
    const a = Math.PI;
    return getDelayedNull();
  }).then(function (_resp) {
    const n = _resp;
    console.log(`${ a }`);
  });
}
test();

The Math.PI doesn't getting logged,

If I reorder the line of const a = Math.PI to after the await statement:

async function test() {
  const n = await getDelayedNull()
  const a = Math.PI
  console.log(`${a}`)
}

Which would prints out the PI successfully.

I suppose both should log the PI in console correctly

Is this a bug? thanks.

performance?

I'm curious to know if you did any benchmarking or if you have any idea how much does this method save over the existing method of compiling to state machines ala regenerator.

Doesn't work with es2015-classes

class Test {
    async test() {
        throw new Error('test-error');
    }
}
      AssertionError [ERR_ASSERTION]: '\'use strict\';\n\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length == 'class Test {\n    test() {\n        return Promise.resolve().then(function () {\n            throw new Error(\'test-error\');\n
      + expected - actual

      -'use strict';
      -
      -var _createClass = function () { function defineProperties(target, props) { for (var i = 0;i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
      -
      -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) {throw new TypeError("Cannot call a class as a function"); } }
      -
      -var Test = function () {
      -    function Test() {
      -        _classCallCheck(this, Test);
      +class Test {
      +    test() {
      +        return Promise.resolve().then(function () {
      +            throw new Error('test-error');
      +        });
           }
      -
      -    _createClass(Test, [{
      -        key: 'test',
      -        value: function test() {
      -            throw new Error('test-error');
      -        }
      -    }]);
      -
      -    return Test;
      -}();
      +}

      at Context.<anonymous> (test/index.js:30:14)

Additional .then call incorrectly inserted

Hello,

I've encountered a case when kneden incorrectly adds extra .then(function () {}); call at the end of the generated async function.

Consider this code:

async function handler() {
    const response = await fetch('http://address');
    if (!response.ok) {
        return null; // 1
    }
    const json = await response.json(); // 2
    return {
      a: 3
    };
}

It produces the following code:

'use strict';

function handler() {
    var response, json;
    return Promise.resolve().then(function () {
        return fetch('http://address');
    }).then(function (_resp) {
        response = _resp;

        if (!response.ok) {
            return null; // 1
        } else {
            return Promise.resolve().then(function () {
                return response.json();
            }).then(function (_resp) {
                json = _resp; // 2

                return {
                    a: 3
                };
            });
        }
    }).then(function () {});
}

The last .then call swallows the last return statement. If you comment out line 1 or 2 then the generated code is correct (no extra .then).

I'm using:
require('babel-core').transform(source, { presets: "es2015", plugins: ["kneden"]}).code
with these dependencies:

  "dependencies": {
    "babel-core": "^6.5.2",
    "babel-preset-es2015": "^6.5.0",
    "kneden": "^1.0.1"
  }

Oh, by the way thanks for this awesome library! The generated code is perfectly readable.

Example in readme improvement

Maybe, I have a feeling I'm missing misunderstanding something, but it would seem to me the example in the readme is incorrect?

Example

In

async function test() {
  await db.destroy();
}

Out

function test() {
  return Promise.resolve().then(function () {
    return db.destroy();
  }).then(function () {});
}

These 2 functions behave differently: the first returns undefined, the second returns a promise?


Perhaps a better example would be:

In

async function test() {
  const result = await db.destroy();
  return result.ok;
}

Out

function test() {
  return Promise.resolve().then(function () {
    return db.destroy();
  }).then(function (result) {
    return result.ok;
  });
}

Does it work like that?

Optimize Promise.resolve().then pattern.

README shows the following code.

function test() {
  return Promise.resolve().then(function () {
    return db.destroy();
  }).then(function () {});
}

I believe this can be optimized into the following (Promise.resolve causes promise to stay as promise, while changing non-promise values into promises).

function test() {
  return Promise.resolve(db.destroy()).then(function () {});
}

This isn't particularly important, because it's purely an optimization, just bringing it up for later.

Incorrect result with async arrow functions when not using `babel-preset-es2015`

I tried kneden out on some test files where I was previously using babel-transform-async-to-generator and came across a failing case.

In this project I wasn't using babel-preset-es2015 as I just wanted to compile async/await down to es6.

I've simplified the test case down to this:

.babelrc

{
  "plugins": ["../../../src"]
}

actual.js

function test() {
  let server;
  before(async () => server = await TestServer.start());
}

The output I get from running the test is:

function test() {
  let server;
  before(() => {
    return Promise.resolve();
  });
}

which is definitely not right :)

If I add "presets": ["es2015"] to .babelrc in the fixture dir I get the more correct looking:

function test() {
  var server = undefined;
  before(function () {
    return Promise.resolve().then(function () {
      return TestServer.start();
    }).then(function (_resp) {
      return server = _resp;
    });
  });
}

Extra-Tick before sync-function is called

Source:

    async wrapCall(fun) {
        const unlock = this.lock();
        let ret;
        try {
            console.log('wrapCall() ' + this._queueCounter);
            ret = await fun();
        } catch (err) {
            // not sucessfull -> unlock before throwing
            unlock();
            throw err;
        }
        // sucessfull -> unlock before return
        unlock();
        return ret;
    },

Is transpiled to:

wrapCall: function wrapCall(fun) {
        var unlock,
            ret,
            _this3 = this;

        return Promise.resolve().then(function () {
            unlock = _this3.lock();
            ret = void 0;
            return Promise.resolve().then(function () {
                console.log('wrapCall() ' + _this3._queueCounter);
                return fun();
            }).then(function (_resp) {
                ret = _resp;
            }).catch(function (err) {
                // not sucessfull -> unlock before throwing
                unlock();
                throw err;
            });
        }).then(function () {
            // sucessfull -> unlock before return
            unlock();
            return ret;
        });
    }

The problem here is that this.lock(); is called after the next tick instead of instantly.
This creates a different side-effect than expected.
Since the lock-call is synchronous it should transpile to something like:

wrapCall: function wrapCall(fun) {
        var unlock,
            ret,
            _this3 = this;
       unlock = _this3.lock();
        return Promise.resolve().then(function () {
         // etc...

```js

Variables goes out of scope prematurely

I had this code:

1 const cid = await getCid(slackUsername)
2 await replyFn(`Creating time report for ${year} ${month} and ${cid}`)
3 const resp = await request.post(config.apiUrl + `/create-time-report/${cid}/${year}/${month}`)

And I got this strange error that cid was undefined on line 3, but it was defined properly on line 2.
After a lot of crying i looked into the generated code and i found that it had been transpiled to:

1  return Promise.resolve().then(function () {
2     return getCid(slackUsername);
3   }).then(function (_resp) {
4    const cid = _resp;
5    return replyFn(`Creating time report for ${year} ${month} and ${cid}`);
6  }).then(function () {
7    return request.post(config.apiUrl + `/create-time-report/${cid}/${year}/${month}`)
8  })

As is clear from the transpiled code cid goes out of scope prematurely.

I am running version 1.0.5 of async-to-promises.

Documentation: "syntax-async-functions" is also required.

Babel doesn't natively support async-await syntax and it produces an error if you try to use async-await syntax in your source files without the syntax-async-functions babel plugin.

So basically instead of having this in your documentation:

.babelrc

{
  "plugins": ["async-to-promises"]
}

It should say this:

.babelrc

{
  "plugins": ["syntax-async-functions", "async-to-promises"]
}

Of course, the change would also be applied it to the "Via CLI", and "Via Node API" code examples as well.

Also, so that people don't have to download both plugins separately, "babel-plugin-syntax-async-functions" should be added to the dependencies list in the package.json file.

Setting local variables inside the promise not outside

So the issue is the following -

This code

async function mainAsync(url) {
    let willReturn = {}
    willReturn.browserPerf = await browserPerf.main(url)
    if (!url.includes("localhost")) {
        willReturn.pageSpeedInsights = await pageSpeedInsights.main(url)
        willReturn.webPageTest = await webPageTest.main(url)
    }
    return willReturn
}

is compiled to

function mainAsync(url) {
    return Promise.resolve().then(function () {
        let willReturn = {};
        return browserPerf.main(url);
    }).then(function (_resp) {
        willReturn.browserPerf = _resp;
        if (!url.includes("localhost")) {
            return Promise.resolve().then(function () {
                return pageSpeedInsights.main(url);
            }).then(function (_resp) {
                willReturn.pageSpeedInsights = _resp;
                return webPageTest.main(url);
            }).then(function (_resp) {
                willReturn.webPageTest = _resp;
            });
        }
    }).then(function () {
        return willReturn;
    });
}

The problem is that the line of defining willReturn is inside the promise and this blocks execution of the code one it reaches _willReturn.browserPerf = _resp_

The line * let willReturn = {};* should be the first line of the function body, not the second.
Otherwise the library is great, congratulations on that.

Support return inside loop

Now it just acts like break. Maybe something like this:

var _break = {}
...
return _break;
// vs.
return;
// and:
return 'some-value'
...

... with a check at the end if _resp === _break.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.