3

Is there a connection between futures and exceptions? async-await looks very similar to throw-catch.

2

Yes, in some implementations of futures: in both cases the (single) thread of execution moves up the call stack on await/throw, and both can be seen as a form of condition, as in the Common Lisp Condition System, and async on a function definition is analogous to throws, specifying the kind of condition it may signal.

However, the closer analogy is that await in an asynchronous function (a kind of promise) is like yield in a generator function, since in both cases it only returns one level up the call stack (rather than automatically propagating), and in both cases the function is resumable, and logically a coroutine.

The key difference between futures and exceptions execution-wise is that typically futures are implemented by resumable conditions, while exceptions are non-resumable conditions.

Concretely, the throw in an exception unwinds the call stack up to the catch (*). After that you can retry from the catch, but not from the throw: you can't resume where it left off.

(*) Implementations differ: often the unwinding only happens if it is caught: uncaught exceptions in C++ don't unwind the stack.

By contrast, the (asynchronous) function that computes a future behaves as a coroutine, like a single-element generator. When you use await it is suspended (like when using yield in a generator), and then it resumes where it left off when it is reactivated. Implementation-wise, the current execution context is saved as a continuation, which is invoked when the promise is reactivated. From the perspective of OOP, the continuation inside promise/generator is an instance of the underlying function. It is possible to implement futures using generators (or coroutines), but it's awkward: calling a single function asynchronously is common, so language support is warranted to make this simple.

More precisely, in the .NET (C#, Visual Basic) implementation of futures (Task), now also used in Dart and to come in JavaScript, when you use await in an async function, it immediately (*) returns a future, just like throw immediately throws an exception. However, a crucial difference is that exceptions automatically propagate (up the call stack) until they hit a catch, while futures only return one level (to the caller). There is no need for a catch with futures, since they only return one level, and await is rather "suspend current function (yield to caller), return future, then resume current function and get value of future when yielded back to". This is complex to state but easy to reason about: the flow is like yield in a generator, with the wrapping in a future (for caller) and then unwrapping a future (from awaited callee).

A function that calls an async function (and receives a future) must either synchronously wait on the future or return the future (in which case it is itself synchronous), or itself be async, in which case there must be a chain of await statements up the call stack, ending in a sync function (since the program as a whole is synchronous, suspended by the OS if it blocks), most commonly the event handler. This allows cooperative multi-tasking, as with other uses of coroutines.

(*) In .NET, await does not suspend if the future value is already available, which is more efficient, while in Dart it always suspends, which is more predictable.

This manual propagation of asynchronicity is in contrast to the automatic propagation of exceptions, and is a very, very good design choice: execution is sequential except where there is an explicit await, which marks where execution is suspended, and makes the interleaving of execution explicit. Contrast with exceptions, where an exception can be thrown at any time, and is not visible at the call site.

In some other implementations futures are implicit: they look just like a normal value of the underlying type, and using the value automatically causes await. This is more elegant at use but makes flow of execution much less clear, because it's not clear when evaluation actually occurs and where it is deferred: using await (resp., returning a future) make these explicit.

In other models, futures are computed by creating a new thread (hence executed in an indeterminate concurrent order, often in parallel), or lazily only when the value is used; neither of these is similar to exceptions.

Lastly, using await on a future may throw an exception, which was thrown (then suspended) by the promise, but this is just due to using exceptions for error-handling, and is otherwise unrelated: it could just as easily return a pair (value, err) as in Go. For example in C#:

Task task = fAsync(); // No try-catch here, as exception is caught by future.
try
{
    await task; // fAsync may throw an exception, which is caught by future, then rethrown by await.
}
catch (SomeException e)
{
    // Handle exception.
}

See also:

  • 1
    So, they are only vaguely similar regarding stack unwinding. So, they are not similar at all. This doesn't seem like an honest attempt of trying to answer your own question, it seems more like you thought something up and made up the question. I'm sorry if this sounds rude. Also, mixing asynchronous models and futures just shows up what to expect from Wikipedia articles. If anything, futures are more similar to result-caching delegates (or lambdas). Wait, that was you editing the Wikipedia article! – acelent Jul 6 '15 at 9:31
  • No offense taken – this was a sincere question, because I read a comment somewhere saying that futures were like exceptions, and it struck me as wrong, but maybe had a kernel of truth, hence tried to understand their thinking. Unstructured control flow is the clear connection, but the differences are starker, and seemed worth clarifying – this is an understandable confusion. – Nils von Barth Jul 7 '15 at 3:54
  • Also, async and futures are distinct, but closely related, and are now especially commonly used together: "When executed, a computation annotated as a future yields a placeholder and introduces an asynchronous thread of control whose result is stored within the associated placeholder." Exceptionally Safe Futures (a bit specific, admittedly) Wiki edits welcome! – Nils von Barth Jul 7 '15 at 3:59
  • Ok, I take part of what I said back, futures, as the term originated are about asynchronous processing and concurrency in an attempt to better exploit opportunities for parallelism. Disclaimer: that downvote is not mine. – acelent Jul 7 '15 at 10:31
  • Nonetheless, there's no similarity to exceptions. Exceptions unwind up, possibly several stack frames; futures have the effect of suspending the current thread (or fiber, whatever) of execution, then executing potentially in concurrency (e.g. more than one needed future yet without value), and returning to the suspended execution point. For a single future, this could be just a regular call. Anyway, they're so different that you can't implement one over the other. – acelent Jul 7 '15 at 10:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.