Add Implicit Async Functions #3757

Merged
merged 47 commits into from Nov 2, 2016

Conversation

Projects
None yet
@GabrielRatener
Contributor

GabrielRatener commented Dec 13, 2014

Temporary fix for bug #3750, tried it, it works.

@GabrielRatener GabrielRatener changed the title from changed jison acceptable versions to change jison acceptable versions Dec 16, 2014

@GabrielRatener

This comment has been minimized.

Show comment
Hide comment
@GabrielRatener

GabrielRatener Dec 19, 2014

Contributor

Here is an outline of my async/await implementation:

The new await keyword implicitly modifies an enclosing function
into an async function which returns a native JS promise when called.
The returned promise resolves to the explicit return value of the function.

When an A-compliant promise is modified with await,
control is passed back to the calling function at that point. The async
function resumes once the function is resolved, with the value of the await <promise> expression being that of the resolved promise.

Similar to generators, in cases where a function needs to be made async
explicitly, await return <some optional expresstion> can be used inside
the function body instead.

When an awaited promise is rejected, a regular error is thrown. These errors can be handled like any other, with try/catch statements.

Since native ES6 promises cannot resolve to a promise, neither can
async function promises. Instead, the first non-thenable value encountered
in the promise resolution chain will be the actual resolution value of the async functions' promise. For example:

    someExplicitlyAsyncFuntion = ->
        # returns a promise that has resolution value 5
        await return Promise.resolve(5)

    do ->
        awaitedValue = await someExplicitlyAsyncFuntion()
        console.log awaitedValue    # logs 5 to the console

Unlike the yield operator, which can work with any kind of expression,
the await operator, works solely on promises. Since no arithmetic operation can
result in a promise in CS/JS, the await operator is given precedence over all arithmetic operators. For example:

    fn = (win, fail) ->
        win(3)

    do ->
        out = 1 + await new Promise(fn) ** 2
        console.log out     # outputs 10 to console

Similar to yield, await cannot occur outside a function body. Additionally,
yield or yield from, and await cannot occur in the same function body, doing so
will result in a compile time error. However in the future this may be enabled for async generators.

Any proposals, modifications, questions, or any other feedback would be really awesome!

Contributor

GabrielRatener commented Dec 19, 2014

Here is an outline of my async/await implementation:

The new await keyword implicitly modifies an enclosing function
into an async function which returns a native JS promise when called.
The returned promise resolves to the explicit return value of the function.

When an A-compliant promise is modified with await,
control is passed back to the calling function at that point. The async
function resumes once the function is resolved, with the value of the await <promise> expression being that of the resolved promise.

Similar to generators, in cases where a function needs to be made async
explicitly, await return <some optional expresstion> can be used inside
the function body instead.

When an awaited promise is rejected, a regular error is thrown. These errors can be handled like any other, with try/catch statements.

Since native ES6 promises cannot resolve to a promise, neither can
async function promises. Instead, the first non-thenable value encountered
in the promise resolution chain will be the actual resolution value of the async functions' promise. For example:

    someExplicitlyAsyncFuntion = ->
        # returns a promise that has resolution value 5
        await return Promise.resolve(5)

    do ->
        awaitedValue = await someExplicitlyAsyncFuntion()
        console.log awaitedValue    # logs 5 to the console

Unlike the yield operator, which can work with any kind of expression,
the await operator, works solely on promises. Since no arithmetic operation can
result in a promise in CS/JS, the await operator is given precedence over all arithmetic operators. For example:

    fn = (win, fail) ->
        win(3)

    do ->
        out = 1 + await new Promise(fn) ** 2
        console.log out     # outputs 10 to console

Similar to yield, await cannot occur outside a function body. Additionally,
yield or yield from, and await cannot occur in the same function body, doing so
will result in a compile time error. However in the future this may be enabled for async generators.

Any proposals, modifications, questions, or any other feedback would be really awesome!

@GabrielRatener GabrielRatener changed the title from change jison acceptable versions to Async/await support on top of enerators Dec 19, 2014

@GabrielRatener GabrielRatener changed the title from Async/await support on top of enerators to Async/await support on top of generators Dec 19, 2014

@GabrielRatener GabrielRatener changed the title from Async/await support on top of generators to Async/await Support on Top of Generators/Yield Dec 19, 2014

@vendethiel

This comment has been minimized.

Show comment
Hide comment
@vendethiel

vendethiel Dec 19, 2014

Collaborator

Amazing. I'm not sure the first commit should be in this PR, though.

Collaborator

vendethiel commented Dec 19, 2014

Amazing. I'm not sure the first commit should be in this PR, though.

@GabrielRatener

This comment has been minimized.

Show comment
Hide comment
@GabrielRatener

GabrielRatener Dec 19, 2014

Contributor

@vendethiel CS does not work properly with newer versions of Jison, see #3750. Until CS is updated to work with a newer version of Jison, shouldn't only v0.2.x be used?

Contributor

GabrielRatener commented Dec 19, 2014

@vendethiel CS does not work properly with newer versions of Jison, see #3750. Until CS is updated to work with a newer version of Jison, shouldn't only v0.2.x be used?

@lydell

View changes

test/async.coffee
+
+
+
+

This comment has been minimized.

@lydell

lydell Dec 19, 2014

Collaborator

What’s up with all these empty lines at the end?

@lydell

lydell Dec 19, 2014

Collaborator

What’s up with all these empty lines at the end?

This comment has been minimized.

@GabrielRatener

GabrielRatener Dec 19, 2014

Contributor

@lydell Not sure, but I want to make the last test more specific anyway, so I will eliminate this in the next commit.

@GabrielRatener

GabrielRatener Dec 19, 2014

Contributor

@lydell Not sure, but I want to make the last test more specific anyway, so I will eliminate this in the next commit.

@GabrielRatener

This comment has been minimized.

Show comment
Hide comment
@GabrielRatener

GabrielRatener Jan 16, 2015

Contributor

@vendethiel The first commit has been purged since #3750 has been fixed.

Contributor

GabrielRatener commented Jan 16, 2015

@vendethiel The first commit has been purged since #3750 has been fixed.

@nfour

This comment has been minimized.

Show comment
Hide comment
@nfour

nfour Jan 16, 2015

SIgn me up, the more control flow options the better at this point!

nfour commented Jan 16, 2015

SIgn me up, the more control flow options the better at this point!

@lydell

View changes

Cakefile
generatorsAreAvailable = '--harmony' in process.execArgv or
'--harmony-generators' in process.execArgv
files.splice files.indexOf('generators.coffee'), 1 if not generatorsAreAvailable
+ files.splice files.indexOf('async.coffee'), 1 if not generatorsAreAvailable

This comment has been minimized.

@lydell

lydell Jan 16, 2015

Collaborator

I would have made an unless generatorsAreAvailable block with the two files.splice lines in it.

@lydell

lydell Jan 16, 2015

Collaborator

I would have made an unless generatorsAreAvailable block with the two files.splice lines in it.

@GabrielRatener

This comment has been minimized.

Show comment
Hide comment
@GabrielRatener

GabrielRatener Jan 16, 2015

Contributor

Considering making resolve an alias or possibly a replacement to await, any thoughts?

Contributor

GabrielRatener commented Jan 16, 2015

Considering making resolve an alias or possibly a replacement to await, any thoughts?

@GabrielRatener

This comment has been minimized.

Show comment
Hide comment
@GabrielRatener

GabrielRatener Jan 29, 2015

Contributor

@Artazor just added support for async constructors, I think this could be quite useful, nice idea!

Contributor

GabrielRatener commented Jan 29, 2015

@Artazor just added support for async constructors, I think this could be quite useful, nice idea!

@Artazor

This comment has been minimized.

Show comment
Hide comment
@Artazor

Artazor Jan 30, 2015

Contributor

@GabrielRatener, great! But I think that we should scan the AST of the constructor and add the implicit return of this at the end, or if there are explicit returns inside the body - wrap their results with some utility like

return a      
       # should be replaced with
return _nonPrimitive(a, this)

where

_nonPrimitive = (value, that) -> 
    if typeof value is 'object'
        value ? that 
    else 
        that ? value  # ? value - for the case when constructor called as function (without new)
Contributor

Artazor commented Jan 30, 2015

@GabrielRatener, great! But I think that we should scan the AST of the constructor and add the implicit return of this at the end, or if there are explicit returns inside the body - wrap their results with some utility like

return a      
       # should be replaced with
return _nonPrimitive(a, this)

where

_nonPrimitive = (value, that) -> 
    if typeof value is 'object'
        value ? that 
    else 
        that ? value  # ? value - for the case when constructor called as function (without new)
@Zolmeister

This comment has been minimized.

Show comment
Hide comment
@Zolmeister

Zolmeister Apr 8, 2015

+1
Status?

+1
Status?

@memelet

This comment has been minimized.

Show comment
Hide comment
@memelet

memelet Jun 7, 2015

+1 This would very nice

memelet commented Jun 7, 2015

+1 This would very nice

@GabrielRatener

This comment has been minimized.

Show comment
Hide comment
@GabrielRatener

GabrielRatener Jun 7, 2015

Contributor

Check out #3813

Contributor

GabrielRatener commented Jun 7, 2015

Check out #3813

@adambiggs

This comment has been minimized.

Show comment
Hide comment

+1

@Jamesernator

This comment has been minimized.

Show comment
Hide comment
@Jamesernator

Jamesernator Jun 15, 2016

I like the syntax, it seems to be working with Promises, but just as a note the specification for ES7 does actually allow awaiting a non promise (tc39/ecmascript-asyncawait#33), so this should actually be valid:

do async ->
    x = await 3
    console.log x # should print 3

You can solve that by simply replacing next.value with Promise.resolve(next.value). More problematic with your code is how you handle errors, which is to say it doesn't. E.G. this does not run:

f = ->
    x = await Promise.resolve(4)
    throw new Error('Whoops')
    return 5

do ->
    try
        a = await f()
        console.log(a)
    catch err
        console.log(err) # This should print [Error: Whoops]

I actually wrote a library a while ago which has a function that should be able to turn any generator into an async function (similar to co but as correct as possible with the ES7 spec whereas co does more magical things) which I believe has a ES7 compliant function for turning any generator-based coroutine into a ES7 asynchronous function.

I cleaned it up and wrote nicer comments so you can take a look at how I handle errors to ensure they always reject as expected, hopefully its useful: https://github.com/Jamesernator/es6-simple-async

Jamesernator commented Jun 15, 2016

I like the syntax, it seems to be working with Promises, but just as a note the specification for ES7 does actually allow awaiting a non promise (tc39/ecmascript-asyncawait#33), so this should actually be valid:

do async ->
    x = await 3
    console.log x # should print 3

You can solve that by simply replacing next.value with Promise.resolve(next.value). More problematic with your code is how you handle errors, which is to say it doesn't. E.G. this does not run:

f = ->
    x = await Promise.resolve(4)
    throw new Error('Whoops')
    return 5

do ->
    try
        a = await f()
        console.log(a)
    catch err
        console.log(err) # This should print [Error: Whoops]

I actually wrote a library a while ago which has a function that should be able to turn any generator into an async function (similar to co but as correct as possible with the ES7 spec whereas co does more magical things) which I believe has a ES7 compliant function for turning any generator-based coroutine into a ES7 asynchronous function.

I cleaned it up and wrote nicer comments so you can take a look at how I handle errors to ensure they always reject as expected, hopefully its useful: https://github.com/Jamesernator/es6-simple-async

@zeekay

This comment has been minimized.

Show comment
Hide comment
@zeekay

zeekay Sep 8, 2016

@GabrielRatener Now that async functions have reached stage 4 status as a proposal and are in the current draft for ES2017, it seems like a pretty great time to finish this PR and get it merged into CoffeeScript finally. The coffeescript6 group has had a lot of success pushing the ball forward with ES6 module support and there has been a lot of discussion around this feature as well. We're also chatting on gitter.

Would you be interested in finishing your efforts here and/or working with us to get async/await support into CoffeeScript?

zeekay commented Sep 8, 2016

@GabrielRatener Now that async functions have reached stage 4 status as a proposal and are in the current draft for ES2017, it seems like a pretty great time to finish this PR and get it merged into CoffeeScript finally. The coffeescript6 group has had a lot of success pushing the ball forward with ES6 module support and there has been a lot of discussion around this feature as well. We're also chatting on gitter.

Would you be interested in finishing your efforts here and/or working with us to get async/await support into CoffeeScript?

@GeoffreyBooth GeoffreyBooth referenced this pull request in coffeescript6/discuss Sep 8, 2016

Closed

CS2 Discussion: Features: async/await #10

test/error_messages.coffee
+ ^^^^^^^
+ '''
+
+test "function cannot contain both `await` and `yieldfrom`", ->

This comment has been minimized.

@lydell

lydell Oct 28, 2016

Collaborator

yield from

@lydell

lydell Oct 28, 2016

Collaborator

yield from

@GabrielRatener

This comment has been minimized.

Show comment
Hide comment
@GabrielRatener

GabrielRatener Oct 29, 2016

Contributor

How about now?

Contributor

GabrielRatener commented Oct 29, 2016

How about now?

@zeekay

This comment has been minimized.

Show comment
Hide comment
@zeekay

zeekay Oct 29, 2016

Given the progress on the async iteration proposal (which is already stage 3 and progressing rapidly), would it be wise to allow yield inside async functions? This would allow us to create async generators as well.

These are easy to polyfill and already supported by the Babel parser and Regenerator project. It would be nice if we could allow that as valid syntax.

zeekay commented Oct 29, 2016

Given the progress on the async iteration proposal (which is already stage 3 and progressing rapidly), would it be wise to allow yield inside async functions? This would allow us to create async generators as well.

These are easy to polyfill and already supported by the Babel parser and Regenerator project. It would be nice if we could allow that as valid syntax.

@GabrielRatener

This comment has been minimized.

Show comment
Hide comment
@GabrielRatener

GabrielRatener Oct 29, 2016

Contributor

@zeekay I'm a huge fan of async iteration, but I think that should be left for a different PR.

Contributor

GabrielRatener commented Oct 29, 2016

@zeekay I'm a huge fan of async iteration, but I think that should be left for a different PR.

@GabrielRatener

This comment has been minimized.

Show comment
Hide comment
@GabrielRatener

GabrielRatener Nov 1, 2016

Contributor

@GeoffreyBooth @lydell Can this be merged now? I see no reason to leave this hanging.

Contributor

GabrielRatener commented Nov 1, 2016

@GeoffreyBooth @lydell Can this be merged now? I see no reason to leave this hanging.

@GeoffreyBooth

This comment has been minimized.

Show comment
Hide comment
@GeoffreyBooth

GeoffreyBooth Nov 1, 2016

Collaborator

Looks good to me. @lydell ?

Collaborator

GeoffreyBooth commented Nov 1, 2016

Looks good to me. @lydell ?

@lydell

lydell approved these changes Nov 2, 2016

@lydell

This comment has been minimized.

Show comment
Hide comment
@lydell

lydell Nov 2, 2016

Collaborator

Looks good to me, too. Hit the green button when you feel like it, @GeoffreyBooth!

Collaborator

lydell commented Nov 2, 2016

Looks good to me, too. Hit the green button when you feel like it, @GeoffreyBooth!

@GeoffreyBooth GeoffreyBooth merged commit 496fd5d into jashkenas:2 Nov 2, 2016

@GeoffreyBooth

This comment has been minimized.

Show comment
Hide comment
@GeoffreyBooth

GeoffreyBooth Nov 2, 2016

Collaborator

Thanks and congrats @GabrielRatener !

Collaborator

GeoffreyBooth commented Nov 2, 2016

Thanks and congrats @GabrielRatener !

@GeoffreyBooth

This comment has been minimized.

Show comment
Hide comment
@GeoffreyBooth

GeoffreyBooth Nov 4, 2016

Collaborator

What about documentation for this?

Collaborator

GeoffreyBooth commented Nov 4, 2016

What about documentation for this?

@GabrielRatener

This comment has been minimized.

Show comment
Hide comment
@GabrielRatener

GabrielRatener Nov 4, 2016

Contributor

This should definitely be documented, especially since this could be a source of confusion as in #4349.

Contributor

GabrielRatener commented Nov 4, 2016

This should definitely be documented, especially since this could be a source of confusion as in #4349.

@GeoffreyBooth

This comment has been minimized.

Show comment
Hide comment
@GeoffreyBooth

GeoffreyBooth Nov 4, 2016

Collaborator

Do you care to write some documentation, perhaps as a new PR for 2?

Collaborator

GeoffreyBooth commented Nov 4, 2016

Do you care to write some documentation, perhaps as a new PR for 2?

@GabrielRatener

This comment has been minimized.

Show comment
Hide comment
@GabrielRatener

GabrielRatener Nov 4, 2016

Contributor

@GeoffreyBooth Yeah, I can open a new PR for that.

Contributor

GabrielRatener commented Nov 4, 2016

@GeoffreyBooth Yeah, I can open a new PR for that.

@GeoffreyBooth GeoffreyBooth referenced this pull request in isagalaev/highlight.js Nov 20, 2016

Merged

Add missing CoffeeScript keywords #1357

@GeoffreyBooth GeoffreyBooth referenced this pull request in coffeescript6/discuss Dec 2, 2016

Closed

CS2 Discussion: Project: Progress as of December 2016 #56

@GeoffreyBooth GeoffreyBooth modified the milestone: 2.0.0 Feb 9, 2017

@kirsle kirsle referenced this pull request in aichaos/rivescript-js Mar 17, 2017

Closed

Async-Await to support User Variable Session Managers #220

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