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

Promise Generators #752

Closed
kkirby opened this issue Jul 9, 2015 · 9 comments
Closed

Promise Generators #752

kkirby opened this issue Jul 9, 2015 · 9 comments

Comments

@kkirby
Copy link

kkirby commented Jul 9, 2015

Hello.

I have updated LiveScript in a fork to support promise generators. The syntax looks like this:

wait = (time) -> new Promise (fulfill) -> setTimeout fulfill, time

my-promise = ->**
    console.log \helloWorld
    yield wait 1000
    console.log \goodbyeWorld

function my-promise **()
    console.log \helloWorld
    yield wait 1000
    console.log \goodbyeWorld

The compiled code renders as such:

// Generated by LiveScript 1.4.0
(function(){
  var wait, myPromise;
  wait = function(time){
    return new Promise(function(fulfill){
      return setTimeout(fulfill, time);
    });
  };
  myPromise = generatorToPromise$(function*(){
    console.log('helloWorld');
    (yield wait(1000));
    return console.log('goodbyeWorld');
  });
  function tryCatch$(func,context,args){
      try {
          return {
              failed: false,
              value: func.apply(context,args)
          };
      }
      catch(exception){
          return {
              failed: true,
              value: exception
          }
      }
  }
  function generatorToPromise$(func,argumentLength){
      function processGenerator(generator){
          return new Promise(
              function(fulfill,reject){
                  function next(passIn,handler){
                      handler = handler || 'next';
                      var tryCatchResult = tryCatch$(generator[handler],generator,[passIn]);
                      if(tryCatchResult.failed === true){
                          return reject(tryCatchResult.value);
                      }
                      var result = tryCatchResult.value;
                      if(result.done){
                          return fulfill(result.value);
                      }
                      else if(result.value instanceof Promise){
                          result.value.then(
                              next,
                              function(promiseError){
                                  next(promiseError,'throw');
                              }
                          );
                      }
                      else {
                          next(result.value);
                      }
                  }
                  next()
              }
          );
      }
      if(typeof func != 'function' && typeof func.next == 'function'){
          return processGenerator(func)
      }
      else {
          if(typeof argumentLength == 'number'){
              var args = [];
              for(var i = 0; i < argumentLength; i++){
                  args.push('arg'+i);
              }
              args = args.join(',');
              return new Function('processGenerator,func','return function(' + args + '){ return processGenerator(func.call(this,' + args + ')); };')(processGenerator,func);
          }
          else {
              return function(){
                  return processGenerator(func.apply(this,arguments));
              }
          }
      }
  }
}).call(this);

My only big concern is using this along with currying. The issue is that currying requires knowing the arguments length of the function, but since all functions are being wrapped, the argument length would be zero. To work around this, I have opted to pass in the argument length if the function is curried and create a function via a string. It's pretty gross, but I can't immediately think of a better way that works across platforms.

You can check the fork out here: https://github.com/kkirby/LiveScript/tree/generator-promise

If promise generators are desired, but modifications are requested, I'm open for discussion. Otherwise, is this something that could become a part of LiveScript?

@kkirby
Copy link
Author

kkirby commented Jul 9, 2015

I was reading around the issues and came across #683 and I'm wondering if it would make more sense to implement this as await/async. I wasn't aware that ES7 had a spec for this. I could keep the syntax the same, but add a new keyword await. Or I could also add an async keyword, but I'm not sure how well that pairs with ->, e.g.: async ->. I guess it doesn't look so bad.

@kkirby
Copy link
Author

kkirby commented Jul 9, 2015

I went ahead and did the async/await approach in another branch. The changes can been seen here: https://github.com/kkirby/LiveScript/tree/async-await.

I realize that async can be considered an ident in other contexts, but as of now, I haven't solved that issue. I thought I did, but I forgot about the possibility of parameters, and my solution wasn't exactly pretty.

Example:

my-async = async (a,b,c) ->
    await readFile()

Result:

(function(){
  var myAsync;
  myAsync = async function(a, b, c){
    return (await readFile());
  };
}).call(this);

@zloirock
Copy link

zloirock commented Jul 9, 2015

Function constructor breaks CSP. Better use template and spawn from ES7 proposal, just replace deprecated Promise.cast to Promise.resolve.

@kkirby
Copy link
Author

kkirby commented Jul 9, 2015

@zloirock So you're thinking LiveScript should handle the transpilation of the syntax sugar itself?

@zloirock
Copy link

zloirock commented Jul 9, 2015

@kkirby I just think LiveScript async functions should be equal ES7 async functions and they should not break CSP.

@kkirby
Copy link
Author

kkirby commented Jul 9, 2015

@zloirock Ohhh. Okay, sorry. Gotcha. I wasn't sure what you meant by CSP. The options were communicating sequential processes or content security policy. I had figured you meant the former since we were talking about async io.

Anyways, if I am understanding you correctly, you're saying that because I'm using new Function in my generatorToPromise helper, that it breaks CSP?

If that's the case, then yes, that's a much better idea. However, since I may end up going the route of just adding in the async/await keywords into LiveScript, it removes the need for the generatorToPromise helper all together.

@zloirock
Copy link

zloirock commented Jul 9, 2015

@kkirby content security policy disallow any form of eval.

@kkirby
Copy link
Author

kkirby commented Jul 10, 2015

@zloirock Thanks for the guidance. I've updated my generator-promise branch to use the spawn method that you recommended. I'm much happier with that method versus my ugly new Function method.

@vendethiel
Copy link
Contributor

I'd like to get a discussion going, but we're not going to do the transpiling ourselves.

What syntax should we chose?

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

No branches or pull requests

3 participants