140
Vote

implement the "await" keyword for async calls

description

From the discussion at http://typescript.codeplex.com/discussions/397594.

Futures/Promises and the await keyword make async call patterns much easier to read and write!

More on "await" here: http://msdn.microsoft.com/en-us/library/hh156528.aspx

file attachments

comments

rwaldron wrote Oct 2, 2012 at 7:13 PM

If anything, generators and yield should be implemented before "await"

danielearwicker wrote Oct 3, 2012 at 10:31 AM

If yield is implemented with same semantics as in Mozilla, then it is practically equivalent to async/await. The yield keyword works like await, and async is not needed (it is implicit).

Key difference from C# yield return is you can say:

var b = yield $.get('blah');

That is, the generator can pass a value out of itself (as in C# yield return), but also when it is resumed it can receive a value (as in C# await). In this case we call jQuery.get which returns a Deferred. So the generator would just need to be managed by a very simple bit of general code that resumes the generator when the Deferred signals it is done.

danielearwicker wrote Oct 3, 2012 at 8:09 PM

Have attached musical support for this request

oisin wrote Dec 6, 2012 at 2:37 PM

@rwaldron - agreed. The demand for "await/async" appears to be driven purely by C# developers unfamiliar/unaware of generators and yield. I fear if await gets pushed into the language, then the cat calls and accusations of "embrace and extend" will no longer be baseless.

oisin wrote Dec 6, 2012 at 2:38 PM

I voted for this myself weeks ago, but now I have repented. Unfortunately there's no way to recall my vote.

bolinfest wrote Dec 31, 2012 at 12:04 AM

@danielearwicker I do not believe that yield is a valid substitute for async/await. When you use yield, the function in which yield is called may be suspended, but control flow is synchronously returned to the calling function.

The point of async/await is that a function that returns a Deferred (or in the case of C#, a Task) can be declared an async function, and its result can be await'ed, during which time the control flow can be passed to another thread of execution. The await'ing code will be resumed when the Deferred gets a value (or fails with an exception) without any additional work by the programmer. This allows a developer to write async code in a linear style.

Although task.js (http://taskjs.org/) suggests that the "await style" can be achieved with yield, it glosses over two important limitations of task.js:

(1) Blocks of code that call async functions must be wrapped up in a no-arg function and passed to spawn().
(2) The scheduling of code blocks passed to spawn() is managed by the task.js library rather than the JavaScript interpreter in which the code is being executed.

(1) is less elegant than built-in language support for async/await, and (2) is likely less-performant than if async/await were supported as part of the interpreter.

I have been trying to wrap my head around the various source translators for this type of thing all day, so please let me know if my assessment of the tradeoffs is inaccurate.

jods wrote Jan 22, 2013 at 9:26 AM

@oisin:
I disagree with the comment stating that await/async is driven "purely by C# developers".

I have been an avid user of IcedCoffeeScript because of this very feature (called await/defer).
Tamejs is another attempt at this, in pure javascript.

Javascript has many asynchronous methods, e.g. anything ajax, and being able to simplify control flow greatly helps in complex cases.

I agree that javascript is not .net, so I'm unsure how exactly it should translate. Maybe IcedCoffeeScript can be an inspiration. Continuation functions are common place in js so they should be supported, not just the deferred pattern.

justinc wrote Feb 18, 2013 at 9:07 PM

You don't even have to like C# to appreciate async/await. No new language should be made without it. It is more than just a coroutine (which is what a function with yield is). They are similar in that they are both rewritten into state machines but the difference is that coroutines have multiple returns while await still has only a single return. Await simply transforms code from a continuation passing style into a synchronous style. Unless there is something special about Mozilla yield that I'm not understanding, I think this is true.

danielearwicker wrote Feb 19, 2013 at 3:39 PM

@justinc
the difference is that coroutines have multiple returns while await still has only a single return.
You can use await many times in an async method, and each time you are effectively feeding a separate Task into a parallel routine. These multiple Tasks are analogous to the multiple values you can feed to Mozilla-yield or C#-yield-return during execution.

You may find this helpful - then again you may not (some of the details are out-of-date because it's based on the ancient CTP code).

@bolinfest
I do not believe that yield is a valid substitute for async/await.
By itself, it's not. But it only needs a simple utility function to provide the plumbing.
When you use yield, the function in which yield is called may be suspended, but control flow is synchronously returned to the calling function.
Depends what you yield. If you yielded a Task<T> you could do exactly what the standard async/await plumbing does. Indeed people have been doing this with yield-return since it was added in C# 2.

The only pain is that whereas I can say:
var result = await SlowCalculation(a, b);
I cannot say:
var result = yield return SlowCalculation(a, b);
Instead I have to say something like:
int result;
yield return SlowCalculation(a, b, v => result = v);
That is, yield return is a statement (void) rather than an expression. And this is because the external interface to a C# iterator method is IEnumerable<T>, which provides no way for the parallel routine to pass in a value that will become the "return value" of yield return.

Mozilla-yield does not suffer from these limitations (the iterator object has a "send" function). In that respect, it more closely resembles C#-await than it does C#-yield-return.

The thing about C# async/await is that it assumes some library-defined plumbing will be present to manage execution. The await keyword is given a Task<T> and now has to decide how to schedule the continuation when the task finishes. Library code makes that decision, and in a GUI application it always schedules continuation on the same GUI thread as it ran on before, whereas in server applications it can use a random thread-pool thread (these defaults can be overridden with library calls).

It would be to Typescript's advantage to leave all that undecided. It can do that by implementing Mozilla-yield. Then as demonstrated here the plumbing for a given scenario can be encapsulated in a simple wrapper function.

So the exact details of how a Deferred works, how to deal with exceptions, etc. can be under the control of libraries, in a very simple, open, transparent way.

jods wrote Feb 20, 2013 at 8:40 AM

@danielearwicker:
yes it is true that your examples using 'yield' and a helper function are close enough.

But keep in mind that this is currently Firefox only. It's a sad state of affairs but none of my clients use firefox. :(
And even inside Firefox, what will happen exactly when ECMAScript Harmony is out is not 100% clear.

Maybe the TypeScript team should wait and see if Generators catch up in other browsers before implementing this complex feature inside their compiler?

But on the other hand it is clear that as of today the problem isn't solved.

jonturner wrote Mar 7, 2013 at 10:54 PM

Agree, while async/await would be very handy, the key thing to get working first would be generators. We're looking into supporting them, though this might come after the 1.0 release. After generators are in place, the code generation could use them, keep the code we output much cleaner than if we did a full-rewriting (a la C#).

LukeH wrote Feb 11 at 5:16 PM

An async/await feature is now proposed for ECMAScript 7, see details on the proposal here: https://github.com/lukehoban/ecmascript-asyncawait. This is a general outline of what TypeScript could provide in advance of engines implementing the feature directly.

soywiz wrote Mar 12 at 10:40 AM

I haven been using a little trick for a while to get this behaviour:

First you have to use Q, promises and node.js >= 0.11 or traceur compiler to get this into the browser.

Then you add to the Q definition from DefinitelyTyped:

declare function yield<T>(promise:__Q.Promise<T>):T;
declare module __Q {
export function spawn<T>(generatorFunction: any):void;
}

then you create a little script that replaces all "Q.spawn(function(" to "Q.spawn(function*(" in all your javascript sources before your application is executed.
Then you use yield as if it was a normal function.

https://github.com/soywiz/heroku-typescript-generators-sample/blob/master/package.json
https://github.com/soywiz/heroku-typescript-generators-sample/blob/master/typings/q/Q.d.ts
https://github.com/soywiz/heroku-typescript-generators-sample/blob/master/bootstrap.ts
https://github.com/soywiz/heroku-typescript-generators-sample/blob/master/updateasync.ts

Then you can create code like this:
https://github.com/soywiz/heroku-typescript-generators-sample/blob/master/test/models_test.ts

Regards.