Reminds me of call/cc, which amuses me since that's the only (major) feature of Scheme I hadn't yet seen anyone calling for in Javascript (eg. there are plenty, including me, who want tail-call optimisation; I've seen pre-compilers implementing hygenic macros, libraries implementing numeric towers, etc.).
I'd like to see TCO in Javascript, but before that I'd like to see some less headache-inducing method of dealing with lots of asynchrony.
I'm not at all sure the method described in the linked article would work as well as said article makes it sound, but I'm definitely in favor of seeing people much smarter than I am argue this one to a finish and then implement the last proposal standing.
This would be wonderful, especially for people learning JavaScript. When and how to use call backs and wrangling asynchronous code propagation is a major frustration coming to JavaScript from languages that default to synchronous behavior.
I'm of two minds on this front. On the one hand, managing asynchronous code in JS is hard. On the other hand, managing asynchronous code in general is hard, and the only difference is that JS makes you think about what it is you're actually doing. I feel like having to make such behaviors explicit in JS has made me a better programmer in other languages because of having had to think through the timing and sequencing issues. It's just like memory management in, e.g., Java - it's not that pointers aren't there, it's just that they're mostly managed for you, and knowing how they work is beneficial to forming a better mental model.
I was thinking the whole way through, "wait, don't generators fix this?" so it was good to see that acknowledged at the end. I'm confused by this, though:
>Unfortunately, using yield based solution will require third party libraries, which means increased payload and incompatibilities among frameworks.
Do you mean that translating to yield requires wrapping things that aren't expecting it? That's true of moving to any non-callback solution. Or do you mean that you want to see the native Node APIs implemented with generators rather than callbacks? I hope the solution to that will simply be patience.
> If you ever had to port localStorage based code to IndexedDB you have been confronted to Javascript's biggest flaw; its asynchronous system is leaky. Once you have an asynchronous method, every other method that calls it must be asynchronous as well.
If I was writing localStorage I would use setTimeout or something and make it async from the beginning. You should always assume async when any type of I/O is happening.
Yes In deed, but I think it is very unfortunate that JavaScript forces you to make your code more complex than it needs to just because the API might change in the future :/
I guess I don't understand the significant advantage this would provide. You can accomplish some of the same functionality with other control flow libraries. Also, I could see the future declaration, with potentially cascading effects, causing as much confusion in the code as callbacks.
The idea of continuations (which promises are) is to make an asynchronous control flow look like it is synchronous, but not become synchronous. More than anything, it is about readability and having a logical flow in the code.
The main reason why writing concurrent code in JavaScript is not completely insane (just moderately so) is because of the single-threaded sequential execution model (also known as cooperative threading).
Asynchronous effects need to remain explicit.
Consider a simple example:
function binary(stack,combine) {
var a = stack.pop();
var b = stack.pop();
stack.push(combine(a,b));
}
In today's JavaScript, this code executes as an atomic sequence. It turns [..,a,b] into [..,combine(a,b)] and there is no way for another piece of code to step on this function's toes and break this invariant.
What if "combine()" suddenly became asynchronous ? Either because its operation itself is asynchronous, or the evaluation of "a" or "b" involved an asynchronous operation. This would cause the call to "binary()" to pause in the middle of the function, which would give time to another part of your code to call "binary()" in turn. And now [a,b,c,d] has turned into [a + b, c + d] instead of [a, b + c + d], and you'll have a jolly good old time debugging tht.
Make asynchronous context switches implicit and you start needing locking primitives. Everywhere, including third party libraries that "used to work". Because every single line of code(except plain assignment) could be a ticking async-bomb that will pause the current function and let other functions wreak havoc on your invariants.
Scary.
Callbacks are ugly, and promises are hardly better, but asynchronous operations NEED to stay explicit. And explicit asynchronous operations are contagious. That's just what they are.
Just make the syntax less right-pyramidal: C#'s async/await syntax is a good candidate, and 'await' is a good way to mark a possible context switch.
I vehemently oppose this proposal. The syntax of the proposed JavaScript addition is confusing and misleading, and will break more programs than JavaScript's unfortunate `this`.
In particular, the following code snipped, copied from the blog post, goes against everything we've been taught by modern programming languages:
var a = future;
var b = a;
console.log(a,b); // will print 'hello' 'world'
b = 'world'; // this does not resolves 'a'
a = 'hello';
It breaks the concept of variables being references to objects. This is similar to Matlab's treatment of matrices, that none of it's successor languages (Python/NumPy, Julia) emulates.
Furthermore, it's confusing in the way that `a` could mean both (1) get value referenced by variable `a`, and (2) wait until value referenced by `a` is available. I believe that a much better proposal would be to treat futures as a separate data type (`a = future()`), and implement getting and fulfilling the future as method calls (`a.set(1)` and `a.wait()`).
I thought/interpreted that the code would stop executing on the line "var b = a", and continue outside the function scope. In fact, your a and b might never be resolved.
Your example illustrates the problem nicely. The code above is impossible to parse without knowing that "future" is a special value. Note that after you assign `a = future` you have no idea that a is special. Separate the code into three modules: the first contains lines 1 and 2, the second contains line 3 and the third contains lines 4 and 5. Now, looking at the second module you have no idea if the execution blocks on `console.log(a,b);` or not.
More importantly, even if all the code is written as nicely as the example above you still cannot reason about this like you could about every other goddamn language out there. You can't pick arbitrary lines of code from the listing and execute them in that order, and if you try to figure out how it works (even if it's written without deadlocks) you will break your brain.
Instead, let's something like $q [1], or even just the smaller/simpler AngularJS implementation of it [2]. While requiring callbacks, it's still very workable and does simplify things. Adding chained callbacks is very nice:
The point is, extending JS to support such constructs without requiring callbacks would be worthwhile, IMO [1]. However, making it obvious with a better API would be necessary.
[1] Note, however, that it would turn JS's threading model completely on its head. Right now, the philosophy of JS is "single-process" threading model, with WebWorkers supporting a "multi-processing" concurrency model by isolating different threads of control. Allowing futures without callbacks would bring JS closer to Golang's concurrency model.
var a = future;
setTimeout(function(){
a = 'Hello World';
},1000);
console.log(a);
If evaluating 'future' returns control to the scheduler, the setTimeout never gets called.
On a higher level, these "flow control" libraries do not, despite their name, control the flow of logic. The underlying asynchrony remains exactly the same. They just make it easier to avoid an indentation avalanche, or callback spaghetti. If and only if spaghetti makes it difficult for you to reason about code will these "flow controls" make it easier to reason about asynchronous Javascript.
As far as I understand it, when the "future" keyword is hit it woudn't return control to the scheduler; it would introduce a special deferred value. Control would only return to the scheduler when something attempts to 'force' that deferred value.
You can think of these deferred values as being a getter and setter pair, sharing a reference:
var future = function() {
var x;
return {'getter': function () {
while (typeof(x) === 'undefined') wait;
return x;
},
'setter': function(val) {
x = val;
}};
};
In the article, future is a keyword rather than a function, and the getter and setter are called implicitly. The idea is that we can pass around references (the {'getter', 'setter'} pair) however we like without triggering any action; however, once the getter is called it will enter a loop, polling and 'waiting' (switching execution back to the scheduler) as long as the setter's not been called.
The radical part here is the 'wait', which I've made up to illustrate the point, and is the crux of the whole thing. It's basically a cooperative multithreading mechanism. This would let an asynchronous API be turned into a synchronous one, since we just need to wait for the result with a polling loop like my example above.
Whether that's a good idea or not is debatable. Personally I think it's a bad idea; shared mutable state is the cause of all concurrency problems (race conditions, locks, deadlock, livelock, etc.) so it absolutely should not be default or implicit. The idea that 'concurrent' == 'multithreaded' needs to die; multithreading is a dangerous anti-feature which uses a little syntax to hide a whole new language semantics, breaking all intuitions about our code and throwing safety properties out of the window.
Thankfully Javascript hasn't fallen into this trap yet; eg. WebWorkers uses message-passing instead.
> And I am also quite disappointed that the solution to one of JavaScripts biggest flaws requires hacking a language feature that was designed for an entirely different use-case, which will surely bring lots of confusion.
There should be a really high bar for adding keywords to a language. If something can be solved with features we already have, we needn't add another. ES6 generators solves the callback problem for now. There are of course other (perhaps more flexible) solutions, like Scala's futures and for-comprehensions [1], or F# computation expressions [2]. But generators are simpler and they work well enough.
ES6 adds function* which is basically a new keyword. This is ugly by the way.
fortunatly on the server I can use node-fibers.on the client,async libs and promises are good enough,as people ususally dont have to write 10 async operations on a row.
A few years ago, I came up with a similar proposal, though more specifically aimed at making callbacks easier to manage. It was a simple syntax extension, as opposed to new semantics for the language.
If synchronous looking code is switching on demand when IO gets blocked, then isn't this just how other language schedulers work (e.g. python, green threads in Java)?
A few years ago it's what everyone liked about node ...
This syntax has been borrowed from other languages and is tried and true.
I feel that the author intuited that something like this would be nice, but wasn't aware of async/await and so proposed something that only kind of vaguely points in the right direction. I don't think he's wrong that something like this would be nice, but async/await is a much more robust way of dealing with this!
This is tangential to the discussion, here, but if you want to make 3 simultaneous async requests, it's super easy with promises. Here's an example using bluebird (https://github.com/petkaantonov/bluebird):
Until ES6 is available everywhere, the existing promises libraries are so mature and easy-to-use that I don't really see a problem with them. Bluebird will even promisify an entire module for you with `promisifyAll`, so mixing it with callback-based modules is typically trivial.
I think that even though it relies on the use of third-party libraries like co, the generators solution is much easier to reason about than the proposed solution here.
I can't find the source, unfortunately, but I remember reading somewhere that generators were actually designed with this use case in mind, so the claim that using them for async "requires hacking a language feature that was designed for an entirely different use-case" is false, if I'm remembering correctly.
PromisifyAll is awesome... until you realize some code you are using is doing some seriously crazy stuff.
For example, Mongoose takes all of methods on the prototype during object creation and replaces them with ones that run middleware. This means that if you try to use one of the promisified methods, none of the middleware gets called.
I made a fix for a mongoose issue that sounds like what you describe and landed it in 1.2.3[1]. Can you verify? And if what you describe is a different issue can you make a github issue? (or is mongoose doing something so crazy that it cannot be helped at all? :P)
Originally I had milder "this could be a problem" objections, but now on my Nth edit I've fallen into "are you trying to drive us all insane" territory.
So here's the (shorter) list of issues, which I'll support later on with code samples:
1. The new behavior is invisible to most readings of the source code. It's just magic, and unless you've obsessively tracked the magic as it flows, you never know where it might be.
2. The magic can strike in any place, at any time, and can throw an exception when it happens. Even `a=b+c` is no longer safe.
3. Even when it doesn't throw an exception, the magic can cause anywhere/anytime pauses and delays. Good luck with that smooth animation function.
4. The assignment semantics mean that even the equals sign is not safe, and can fuck up my code.
5. Infinite loops?
6. Indefinite resolution?
______________
#1:
Should be self-evident. There's no way to know, reading code, whether a variable is a future, unless you backtrack it alllll the way. There are no annotations or markings as it flows.
That would be par-for-the-course in JS and fine except that we're not just talking about another scalar type, this is a whole new beast.
#2:
future function getCount(){
raise "OH GOD THE NETWORK IS DOWN";
}
var b = 7;
var c = getCount();
var a = b + c; // How was I supposed to expect an exception here!?
#3:
var congratulationAnimation = function(msg){
// Spend 1 second fading in different UI elements
var escaped = escapeForHtml(msg);
// Spend 1 second fading in message
// Spend 1 second fading in remainder of UI elements
};
var msg = getMessage(); // Secretly a future from somewhere
congratulationAnimation(msg);
// WTF, why is my animation pausing halfway!?!?
#4:
var msg = future;
alert(msg);
// [...elsewhere...]
// Store heroic status update
msg = "Today I beat up a baby";
msg += " godzilla which was terrorizing a local school";
// OH GOD THEY THINK I'M A VILLAIN NOW
#5:
// I have no clue what this'll do.
var a = future;
a = 1 + a;
#6:
// What does this do, break console output forever?
var f = function(){
var a = future;
console.log(a);
}
f();
I don't like this. Even the author's own example shows how this gets very confusing even with simple code:
var a = future;
console.log(a); // will print 'hello'
a = 'Hello'
a += ' World!'
console.log(a); // will print 'Hello World!'
Deferreds/promises are good exactly because they explicitly state what they are through their type, and they behave the "same" whether they are resolved or not.
This proposal breaks the notion of what a variable is, and it behaves very differently whether the future has been replaced by a value or not. This will become confusing very fast and a great source of race conditions.
Ugh. There are (at least) two fundamental problems here:
First, depsite the ugliness that asynchronous code entails, it does have one very
important feature: it never blocks, which makes reasoning about it much easier.
This proposal would make it possible for Javascript code to block, which enables
a whole class of bugs that were impossible before.
var a = future;
var b = future;
setTimeout(function() {
a = b+1;
},0);
setTimeout(function() {
b = a+1;
},0);
console.log(a, b);
Hello deadlocks!
Second, futures aren't first-class values. The proposal specifies that assigning
a future to a variable doesn't create a second reference to the future, it creates
a child future that would be resolve by the resolution of the parent future.
Thus, a future must be resolved in the scope in which it's created, or the
resolution won't be propagated to all its children. With promises, you dont' have
to worry about the side effects of assignment or function calls. Take this
common pattern for wrapping an asynchronous call with a promise:
function promiseMeSomething() {
var d = defer();
doSomethingAsyncronous(function(err, value) {
d.resolve(value);
});
return d.promise;
}
We could do the same with futures:
function futureMeSomething() {
var f = future;
doSomethingAsyncronous(function(err, value) {
f = value;
});
return f;
}
But that callback function is repetitive, so we'll refactor:
function makeCallback(d) {
return function(err, value) {
d.resolve(value);
}
}
function promiseMeSomething() {
var d = defer();
doSomethingAsyncronous(makeCallback(d));
return d.promise;
}
But consider the same refactoring with futures:
function makeCallback(g) {
return function(err, value) {
g = value;
}
}
function futureMeSomething() {
var f = future;
doSomethingAsyncronous(makeCallback(f));
return f;
}
Whoops. This won't work, because g is a child future of
f and resolving it won't resolve f.
As well, Javascript's single-threaded execution means blocking execution halts your whole program. IMO, the way to tackle complex asynchronous callback code is with smarter code structure and frameworks — just as you should avoid shoe-horning inheritance OOP into JS, you should write async callback code with readability and flexibility in mind (without complaining you "have to" write bad code)
Genereally speaking async is one of those things that the JS community hasn't quite figured out yet. (Nor has anyone else, really.)
Personally, what I'd really like to see is an M:1 thread model, i.e. a model where you program as if you have M threads of control, but only one of them is actually executing at any one time. Any calls which are currently async would behave as if blocking and would yield control to any other runnable "thread". So you would have concurrency but not parallelism -- which is effectively what you have to reason about anyway when you have async.
It may very well just be me, but I find that reasoning about normal "blocking" flow control is vastly easier in general than reasoning about async flow control even if it involves multiple threads. It gets even easier if you limit all inter-thread communication to some form of queueing/message passing.
EDIT: Reading it back this looks overly convoluted, basically what I'd like in JS is "user-space threads".
I have been using Jquery promises with good enough success. And the chaining makes the code synchronous to read.
Improve them slightly add error handling like maybe monad. Yield has done wonders for the delegate allergic people in C# for ages.
We probably should abstain for a while to change the JS language - it is quite messy as it is. Undescore is awesome example how you can extend the language with small amounts of code.
I think what you are looking for is done, to some extent, in angular.js with their resource library. The future object is returned from the resource and can be put directly into a template, which then re-renders when the response comes from the server. The problem I have found is that it does not clean up your javascript code as much as you would think. For example, error handling is still complicated.
I have had good luck using javascript promise objects to clean up complicated async calls.
for example, making multiple calls and waiting for all responses looks something like this:
...or we can wait for one to finish and then make a couple more calls...
promise = new Promise('/restaurant/1');
promise.then(function(r) {
return YourFavoriteLibAgain.all([
new Promise('/restaurant/' + r.id + '/patrons'),
new Promise('/restaurant/' + r.id + '/menu')
]);
})
.then(function(res) {
// do more fun things synchronously
});
The details change, and some libraries act a little differently, but once you break they async calls into steps it becomes easy to read.
I think the point of all of these responses is that async cannot look like regular statements because they do not act like regular statements.
I've been actively programming in JS since it was in beta and this proposal seems really confusing to me. I can only imagine what a new programmer would think when they encountered `var a = future`.
And I know this seems petty, and for that I apologize, but... "excape"?
43 comments
[ 3.4 ms ] story [ 48.6 ms ] threadI'm not at all sure the method described in the linked article would work as well as said article makes it sound, but I'm definitely in favor of seeing people much smarter than I am argue this one to a finish and then implement the last proposal standing.
[0] http://okmij.org/ftp/continuations/against-callcc.html
>Unfortunately, using yield based solution will require third party libraries, which means increased payload and incompatibilities among frameworks.
Do you mean that translating to yield requires wrapping things that aren't expecting it? That's true of moving to any non-callback solution. Or do you mean that you want to see the native Node APIs implemented with generators rather than callbacks? I hope the solution to that will simply be patience.
If I was writing localStorage I would use setTimeout or something and make it async from the beginning. You should always assume async when any type of I/O is happening.
The point of Node is that it is asynchronous.
Asynchronous effects need to remain explicit.
Consider a simple example:
In today's JavaScript, this code executes as an atomic sequence. It turns [..,a,b] into [..,combine(a,b)] and there is no way for another piece of code to step on this function's toes and break this invariant.What if "combine()" suddenly became asynchronous ? Either because its operation itself is asynchronous, or the evaluation of "a" or "b" involved an asynchronous operation. This would cause the call to "binary()" to pause in the middle of the function, which would give time to another part of your code to call "binary()" in turn. And now [a,b,c,d] has turned into [a + b, c + d] instead of [a, b + c + d], and you'll have a jolly good old time debugging tht.
Make asynchronous context switches implicit and you start needing locking primitives. Everywhere, including third party libraries that "used to work". Because every single line of code(except plain assignment) could be a ticking async-bomb that will pause the current function and let other functions wreak havoc on your invariants.
Scary.
Callbacks are ugly, and promises are hardly better, but asynchronous operations NEED to stay explicit. And explicit asynchronous operations are contagious. That's just what they are.
Just make the syntax less right-pyramidal: C#'s async/await syntax is a good candidate, and 'await' is a good way to mark a possible context switch.
In particular, the following code snipped, copied from the blog post, goes against everything we've been taught by modern programming languages:
It breaks the concept of variables being references to objects. This is similar to Matlab's treatment of matrices, that none of it's successor languages (Python/NumPy, Julia) emulates.Furthermore, it's confusing in the way that `a` could mean both (1) get value referenced by variable `a`, and (2) wait until value referenced by `a` is available. I believe that a much better proposal would be to treat futures as a separate data type (`a = future()`), and implement getting and fulfilling the future as method calls (`a.set(1)` and `a.wait()`).
More importantly, even if all the code is written as nicely as the example above you still cannot reason about this like you could about every other goddamn language out there. You can't pick arbitrary lines of code from the listing and execute them in that order, and if you try to figure out how it works (even if it's written without deadlocks) you will break your brain.
Instead, let's something like $q [1], or even just the smaller/simpler AngularJS implementation of it [2]. While requiring callbacks, it's still very workable and does simplify things. Adding chained callbacks is very nice:
[1] https://github.com/kriskowal/q/wiki/API-Reference[2] https://docs.angularjs.org/api/ng/service/$q
[1] Note, however, that it would turn JS's threading model completely on its head. Right now, the philosophy of JS is "single-process" threading model, with WebWorkers supporting a "multi-processing" concurrency model by isolating different threads of control. Allowing futures without callbacks would bring JS closer to Golang's concurrency model.
var a = future; setTimeout(function(){ a = 'Hello World'; },1000); console.log(a);
If evaluating 'future' returns control to the scheduler, the setTimeout never gets called.
On a higher level, these "flow control" libraries do not, despite their name, control the flow of logic. The underlying asynchrony remains exactly the same. They just make it easier to avoid an indentation avalanche, or callback spaghetti. If and only if spaghetti makes it difficult for you to reason about code will these "flow controls" make it easier to reason about asynchronous Javascript.
- passing it as an argument to an operator or built-in function
- accessing one of its its members
- calling it as a function
You can think of these deferred values as being a getter and setter pair, sharing a reference:
In the article, future is a keyword rather than a function, and the getter and setter are called implicitly. The idea is that we can pass around references (the {'getter', 'setter'} pair) however we like without triggering any action; however, once the getter is called it will enter a loop, polling and 'waiting' (switching execution back to the scheduler) as long as the setter's not been called.The radical part here is the 'wait', which I've made up to illustrate the point, and is the crux of the whole thing. It's basically a cooperative multithreading mechanism. This would let an asynchronous API be turned into a synchronous one, since we just need to wait for the result with a polling loop like my example above.
Whether that's a good idea or not is debatable. Personally I think it's a bad idea; shared mutable state is the cause of all concurrency problems (race conditions, locks, deadlock, livelock, etc.) so it absolutely should not be default or implicit. The idea that 'concurrent' == 'multithreaded' needs to die; multithreading is a dangerous anti-feature which uses a little syntax to hide a whole new language semantics, breaking all intuitions about our code and throwing safety properties out of the window.
Thankfully Javascript hasn't fallen into this trap yet; eg. WebWorkers uses message-passing instead.
Check out co (https://github.com/visionmedia/co/blob/master/index.js). For all its features it's tiny. Increased payload and incompatibility is a blanket statement; that could apply to any library or framework.
> And I am also quite disappointed that the solution to one of JavaScripts biggest flaws requires hacking a language feature that was designed for an entirely different use-case, which will surely bring lots of confusion.
There should be a really high bar for adding keywords to a language. If something can be solved with features we already have, we needn't add another. ES6 generators solves the callback problem for now. There are of course other (perhaps more flexible) solutions, like Scala's futures and for-comprehensions [1], or F# computation expressions [2]. But generators are simpler and they work well enough.
[1] http://docs.scala-lang.org/overviews/core/futures.html [2] http://fsharpforfunandprofit.com/series/computation-expressi...
You would write this:
And JavaScript would parse it as: This is obviously shamelessly pilfered from monadic syntax 'let!' in F#, and my own 'let!' extension in OCaml.A few years ago it's what everyone liked about node ...
https://github.com/clojure/core.async/blob/master/examples/w...
http://wiki.ecmascript.org/doku.php?id=strawman:async_functi...
I feel that the author intuited that something like this would be nice, but wasn't aware of async/await and so proposed something that only kind of vaguely points in the right direction. I don't think he's wrong that something like this would be nice, but async/await is a much more robust way of dealing with this!
I think that even though it relies on the use of third-party libraries like co, the generators solution is much easier to reason about than the proposed solution here.
I can't find the source, unfortunately, but I remember reading somewhere that generators were actually designed with this use case in mind, so the claim that using them for async "requires hacking a language feature that was designed for an entirely different use-case" is false, if I'm remembering correctly.
For example, Mongoose takes all of methods on the prototype during object creation and replaces them with ones that run middleware. This means that if you try to use one of the promisified methods, none of the middleware gets called.
[1] https://github.com/petkaantonov/bluebird/commit/70f90c33dd94...
So here's the (shorter) list of issues, which I'll support later on with code samples:
1. The new behavior is invisible to most readings of the source code. It's just magic, and unless you've obsessively tracked the magic as it flows, you never know where it might be.
2. The magic can strike in any place, at any time, and can throw an exception when it happens. Even `a=b+c` is no longer safe.
3. Even when it doesn't throw an exception, the magic can cause anywhere/anytime pauses and delays. Good luck with that smooth animation function.
4. The assignment semantics mean that even the equals sign is not safe, and can fuck up my code.
5. Infinite loops?
6. Indefinite resolution?
______________
#1:
Should be self-evident. There's no way to know, reading code, whether a variable is a future, unless you backtrack it alllll the way. There are no annotations or markings as it flows.
That would be par-for-the-course in JS and fine except that we're not just talking about another scalar type, this is a whole new beast.
#2:
#3: #4: #5: #6:This proposal breaks the notion of what a variable is, and it behaves very differently whether the future has been replaced by a value or not. This will become confusing very fast and a great source of race conditions.
First, depsite the ugliness that asynchronous code entails, it does have one very important feature: it never blocks, which makes reasoning about it much easier. This proposal would make it possible for Javascript code to block, which enables a whole class of bugs that were impossible before.
Hello deadlocks!Second, futures aren't first-class values. The proposal specifies that assigning a future to a variable doesn't create a second reference to the future, it creates a child future that would be resolve by the resolution of the parent future.
Thus, a future must be resolved in the scope in which it's created, or the resolution won't be propagated to all its children. With promises, you dont' have to worry about the side effects of assignment or function calls. Take this common pattern for wrapping an asynchronous call with a promise:
We could do the same with futures: But that callback function is repetitive, so we'll refactor: But consider the same refactoring with futures: Whoops. This won't work, because g is a child future of f and resolving it won't resolve f.Personally, what I'd really like to see is an M:1 thread model, i.e. a model where you program as if you have M threads of control, but only one of them is actually executing at any one time. Any calls which are currently async would behave as if blocking and would yield control to any other runnable "thread". So you would have concurrency but not parallelism -- which is effectively what you have to reason about anyway when you have async.
It may very well just be me, but I find that reasoning about normal "blocking" flow control is vastly easier in general than reasoning about async flow control even if it involves multiple threads. It gets even easier if you limit all inter-thread communication to some form of queueing/message passing.
EDIT: Reading it back this looks overly convoluted, basically what I'd like in JS is "user-space threads".
Improve them slightly add error handling like maybe monad. Yield has done wonders for the delegate allergic people in C# for ages.
We probably should abstain for a while to change the JS language - it is quite messy as it is. Undescore is awesome example how you can extend the language with small amounts of code.
I have had good luck using javascript promise objects to clean up complicated async calls.
for example, making multiple calls and waiting for all responses looks something like this:
promise = YourFavoriteAsyncLib.all([new Promise('/restaurants'), new Promise('/shops')]) promise.then(function(res) { // res[0] === restaurants // res[1] === shops });
...or we can wait for one to finish and then make a couple more calls...
promise = new Promise('/restaurant/1');
promise.then(function(r) { return YourFavoriteLibAgain.all([ new Promise('/restaurant/' + r.id + '/patrons'), new Promise('/restaurant/' + r.id + '/menu') ]); }) .then(function(res) { // do more fun things synchronously });
The details change, and some libraries act a little differently, but once you break they async calls into steps it becomes easy to read.
I think the point of all of these responses is that async cannot look like regular statements because they do not act like regular statements.
And I know this seems petty, and for that I apologize, but... "excape"?