SLaks.Blog

Making the world a better place, one line of code at a time

Concurrency, part 4: Comparing promises frameworks in different languages

Posted on Thursday, January 08, 2015

Last time, I explained what promises are and how they work. Now, I’ll explore standard & third-party promise libraries in popular languages, explaining how they implement each facet of the promise paradigm.

.Net languages (C#, VB, etc…)

The .Net base class library includes promise classes called Task and Task<TResult>. These classes were introduced in .Net 4.0, released in 2010. Task is used for promises which have no value (simply running callbacks once the operation is complete); Task<TResult> inherits Task and exposes a value as well.

Methods

Behavior

By default, .ContinueWith() will always run the callback asynchronously (using the current TaskScheduler), avoiding reentrancy bugs. You can override this behavior by passing TaskContinuationOptions.ExecuteSynchronously.

Tasks also support cancellation. Cancellation is a third fulfillment state that is mostly parallel to rejection. When creating a new Task from a TaskCompletionSource, you can call SetCancelled to set the resulting task to canceled. If a .ContinueWith() callback throws a TaskCancelledException or OperationCanceledException, the resulting task will also be canceled. Cancellation is designed to be used together with the CancellationSource system to cancel operations and callbacks.

Since .Net is a multi-threaded environment, you can also synchronously wait for task to finish by calling .Wait(). This will block the calling thread until the task is fulfilled, so it should not be called on a UI thread or in highly multi-threaded environments when threads are scarce.

Most confusingly, the Task class can also represent a delegate running on a background thread. You can create a new task from a delegate, which will return an unstarted task object. Calling Start() on this instance will run the delegate on its TaskScheduler and eventually resolve the task to its result. This use (and the entire Start() method) is confusing and should be avoided; instead, simply call Task.Run().

C# 5.0 & VB 11.0 took this one step further with language-level asynchrony using the await keyword, allowing you work with promises just like you would write synchronous code. Behind the scenes, the compiler transforms async code into a series of promise callbacks. Jon Skeet wrote a series of blog posts explaining exactly how this transformation works.

Availability

.Net 4.5 introduced *TaskAsync() versions of most of the framework’s non-CPU-bound operations, including stream IO, HTTP requests, database connections, and more. Note that the async methods in DbConnection & DbCommand will by default run synchronously and return a pre-completed task; it’s up to the derived connection clients to override these methods with non-blocking implementations. The built-in SqlConnection does override these methods; other ADO.Net providers may or may not.

All modern .Net unit testing frameworks have some level of support for async tests; for more details, see Stephen Cleary’s article on async .Net unit tests. Recent versions of ASP.Net MVC and Web API also allow async actions that return Tasks; ASP.Net vNext (6.0) also adds support for async action filters. Entity Framework also added support for async queries and updates (assuming the underlying provider implements async correctly) in version 6.

Outside Microsoft’s standard libraries, the situation is murkier. Older open source libraries for network-bound operations frequent lack async methods or implement them incorrectly (by running a blocking call on a thread pool thread instead of a non-blocking call). Newer libraries are more likely to get this right, especially with the introduction of language-level async support in 2012. Stephen Cleary wrote an excellent set of guidelines on how to properly write async APIs.

Java 7 – Guava

Java’s promise story is murkier. Java 1.5 introduced the Future<T> interface, which looks like a promise, but actually isn’t. A Future<T> does indeed represent a value which will be computed later, but it offers no way to notify upon completion – it has no then() method. All you can do with a future is synchronously wait for it to be completed. Thus, it is completely useless for non-blocking asynchronous operations.

To solve this, Google’s Guava library has its own promise-like interface called ListenableFuture<T>. This interface extends the standard Future<T> and adds a single addListener() method that takes a Runnable callback and an Executor to run the callback with (letting you control whether the callback will run synchronously or not). This is an extremely simple API designed to be easy to implement; the callback will run on both success and failure and does not allow chaining. Instead, the full richness & convenience of the usual promise APIs are available as static methods in the Futures class. x ## Methods By default, callback methods will run synchronously. You can make them run asynchronously by passing an Executor.

For more information, see Guava’s introduction to ListenableFuture.

Java 8

Java 8 finally introduced a built-in promise interface, CompletionStage<T>.

Methods

Every method in CompetionStage has three variants: method(callback) runs its callback synchronously, methodAsync(callback) runs the callback asynchronously on the common ForkJoinPool, and methodAsync(callback, executor) runs the callback asynchronously on the specified executor.

Availability

Unfortunately, CompletionStage is not used anywhere in the JDK standard library. There are third-party async libraries that return CompletionStages.

Javascript

Javascript has a much richer ecosystem of promises. Because Javascript is strictly single-threaded, JS programmers have had to deal with asynchrony since day 1. Different asynchronous APIs have taken different approaches to this; earlier browser APIs such as XmlHttpRequest uses events, and Node.js uses callbacks everywhere. Promises slowly began to arrive onto this landscape, including jQuery’s original version of $.Deferred or Closure’s goog.async.Deferred, which are mostly, but not entirely, like promises. Later came a variety of Node.js and/or browser-based promise libraries, including Q, Bluebird, and many others. Most recently, the upcoming ECMAScript 6 standard includes a Promise class (although many third-party implementations such as Q have richer APIs).

To simplify this mess, Kris Kowal, Domenic Denicola, and others put together the Promises/A+ spec, which codifies exactly how Javascript promises should behave. Promises/A+ specifies how promise resolution should work, and the exact signature and behavior of the then() method for adding success & error callbacks and chaining. It leaves everything else up to the individual implementations; most notably, it makes no mention of how to create a promise in the first place.

All modern promise implementations, including Q, Bluebird, Angular’s $q (which is based on Q), Bluebird, Closure’s goog.Promise, but with the notable exception of jQuery, including the ES6 promise class implement Promises/A+, and also add promise creation and convenience methods. The Promises/A+ site has a full list of conforming implementations.

Methods

For more details, consult the documentation for your specific implementation.

Availability

There are no standard APIs that return promises (yet). However, there are a large variety of open-source libraries (for Node.js and the browser) that expose promise-based APIs.

One of the advantages of the Promises/A+ standard is that all every standards-compliant implementation should be interoperable. You can take a promise from one implementation, and return it from a then() callback, or pass it to all() or race(), in a different implementation, and everything should work fine. This is particularly useful when consuming a library that uses a different promise implementation than your code uses.

jQuery Promises

jQuery introduced its own promise-like abstraction in version 1.5, $.deferred(). This feature was introduced before promises had a widespread standard API, and its API changed substantially in version 1.8 to align more closely with other promise libraries. jQuery still has one notable difference; promise callbacks are always executed synchronously (directly within resolve() or then()). jQuery deferreds also support the notion of a progress callback, which can be invoked multiple times before the promise is resolved to report incremental progress.

Methods

Availability

jQuery returns promises from all of its asynchronous operations. $.ajax() (and equivalent helper methods like $.get(), $.getJSON(), or $.getScript()) return jqXHR objects which are promises with additional AJAX-related properties. jQuery objects also have a .promise() method which returns a promise of the end of the element(s)’s animation queue. This lets you easily wait for animations to finish running before continuing with other code via promise chains.

Next time: Advanced promise development guidelines

Categories: async, promises, concepts, javascript Tweet this post

comments powered by Disqus