Using Deferred Objects and Promises with Java 8 and JDeferred

September 27th, 2015 by

Promises may help us when dealing with asynchronous code and we need to merge, pipe or track the progress and the results of single parts of computation in our applications.

In the following tutorial I’d like to demonstrate a small library, JDeferred that helps us for this specific use case.

JDeferred examples running in Eclipse IDE.

JDeferred examples running in Eclipse IDE.

 

Dependencies

Using Maven here, we simply need to add one dependency for jdeferred-core to our pom.xml:

<dependency>
	<groupId>org.jdeferred</groupId>
	<artifactId>jdeferred-core</artifactId>
	<version>1.2.4</version>
</dependency>

What is a Deferred-Object and what a Promise?

A Promise is used for deferred and asynchronous computations and, similar to Java’s native Future object, represents the result of an operation that has not been completed yet.

Pending, Rejected and Fulfilled are the three valid states for a Promise and for each state, event handlers may be attached and Promises may be chained.

More information about this concept may be found on the Mozilla website.

In the terminology of this library, Promises are immutable versions of DeferredObjects. As the DeferredObject grants access to the resolve and reject methods, most library code should return a Promise so that users do not mess around with the internals here.

Example Classes

To demonstrate the usage of JDeferred here I have written two sample classes that hold an inner instance of Deferred and report changes of their state (progress, fail, completion ..) to this instance.

In addition they provide a getter to access the specific promise. The promise itself is immutable, the deferred is not, so we should be careful not to pass the deferred object around.

The design of the following examples classes is quite bad (mutable state, state handling, thread-safety etc..) but should be sufficient for this tutorial to demonstrate JDeferred :)

Multiplier

Our first class simply multiplies a given number a designated number of times.

Each round when the number is multiplied again, the state of the deferred object is updated with information about the progress (completion in %).

package com.hascode.tutorial;
 
import org.jdeferred.Deferred;
import org.jdeferred.Promise;
import org.jdeferred.impl.DeferredObject;
 
public class Multiplier {
	private final long factor;
	private final Deferred<Long, Long, String> deferred = new DeferredObject<>();
 
	public Multiplier(long factor) {
		this.factor = factor;
	}
 
	public long multiplyNTimes(int rounds) {
		long result = 1;
		for (int i = 1; i <= rounds; i++) {
			deferred.notify("status: " + (i * 100 / rounds) + "%");
			result *= factor;
		}
		deferred.resolve(result);
		return result;
	}
 
	public Promise<Long, Long, String> promise() {
		return deferred.promise();
	}
}

WebCrawler

The other ugly class is a stateful simulated web-crawler that fetches a list of key-words from a given website (by given URL string).

package com.hascode.tutorial;
 
import java.util.ArrayList;
import java.util.List;
 
import org.jdeferred.Deferred;
import org.jdeferred.Promise;
import org.jdeferred.impl.DeferredObject;
 
public class WebCrawler { // ugly, stateful, demonstration only ;)
	private final Deferred<List<String>, String, Integer> deferred = new DeferredObject<>();
	private List<String> keywordsFound = new ArrayList<>();
 
	public void crawl(String url) {
		initializeCrawler(); // fake
		deferred.notify(10);
		fetchSiteContent(url);// fake
		deferred.notify(20);
		filterDuplicateKeywords();// fake
		deferred.notify(80);
		storeKeywords();// fake
		deferred.resolve(keywordsFound);
	}
 
	public Promise<List<String>, String, Integer> promise() {
		return deferred.promise();
	}
 
	private void storeKeywords() {
		System.out.println("storing keywords");
		keywordsFound.add("foo");
		keywordsFound.add("bar");
		keywordsFound.add("baz");
	}
 
	private void filterDuplicateKeywords() {
		System.out.println("filtering duplicate keywords");
	}
 
	private void fetchSiteContent(String url) {
		System.out.println("fetching site content for url: " + url);
	}
 
	private void initializeCrawler() {
		System.out.println("initializing crawler");
	}
}

Now we’re ready to play around with promises..

Simple Promises

In the first example, we’re binding simple promises to both demonstration classes.

On progress we’re printing the progress information to STDOUT, finally we’re displaying the computation result of the multiplication (in the first example) and display information about keywords found (in the second example).

Java 8′s lambda expressions spare us using anonymous classes here (as long as we don’t get any type ambiguity ;):

package com.hascode.tutorial;
 
public class SimplePromiseExample {
 
	public static void main(String[] args) {
		System.out.println("## Example 1");
		Multiplier m = new Multiplier(5);
		m.promise().progress(System.out::println).done((res) -> System.out.println("the result is: " + res));
		m.multiplyNTimes(4);
 
		System.out.println("## Example 2");
		WebCrawler crawler = new WebCrawler();
		crawler.promise().progress((i) -> System.out.println("Progress: " + i + "%")).done((keywords) -> {
			System.out.println("Done, " + keywords.size() + " keywords found: ");
			keywords.forEach(System.out::println);
		});
		crawler.crawl("https://www.hascode.com/");
 
	}
}

Running the code above in our IDE of choice or using Maven in the console should produce a similar output:

$ mvn exec:java -Dexec.mainClass=com.hascode.tutorial.SimplePromiseExample
[..]
status: 25%
status: 50%
status: 75%
status: 100%
the result is: 625
## Example 2
initializing crawler
Progress: 10%
fetching site content for url: https://www.hascode.com/
Progress: 20%
filtering duplicate keywords
Progress: 80%
storing keywords
Done, 3 keywords found:
foo
bar
baz

Promise Filtering

We may add a filter to a promise to manipulate the result of its computation.

In the following example, we’re sorting the keywords found alphabetically using a DoneFilter.

package com.hascode.tutorial;
 
import java.util.Collections;
import java.util.List;
 
import org.jdeferred.Promise;
 
public class PromiseFilterExample {
 
	public static void main(String[] args) {
		WebCrawler crawler = new WebCrawler();
		Promise<List<String>, String, Integer> promise = crawler.promise()
				.progress((i) -> System.out.println("Progress: " + i + "%")).done((keywords) -> {
					System.out.println("Done, " + keywords.size() + " keywords found: ");
					keywords.forEach(System.out::println);
				});
 
		Promise<List<String>, String, Integer> filteredPromise = promise.then((keywords) -> Collections.sort(keywords));
		filteredPromise.done((keywords) -> {
			System.out.println("Done (sorted by filter), " + keywords.size() + " keywords found: ");
			keywords.forEach(System.out::println);
		});
 
		crawler.crawl("https://www.hascode.com/");
	}
}

Running the code above in our IDE of choice or using Maven in the console should produce a similar output:

$ mvn exec:java -Dexec.mainClass=com.hascode.tutorial.PromiseFilterExample
[..]
initializing crawler
Progress: 10%
fetching site content for url: https://www.hascode.com/
Progress: 20%
filtering duplicate keywords
Progress: 80%
storing keywords
Done, 3 keywords found:
foo
bar
baz
Done (sorted by filter), 3 keywords found:
bar
baz
foo

Pipes

Pipes allow us to react to the outcome of a promises computation.

In the following example, we’re using a DonePipe to handle different results of our multiplication class.

  • If the result is less than 50, we’re rejecting the result and return an exception
  • If the result is less than 130, we’re modifying the result by multiplying it with 2
  • Else we’re returning the original result
package com.hascode.tutorial;
 
import java.util.stream.IntStream;
 
import org.jdeferred.DonePipe;
import org.jdeferred.Promise;
import org.jdeferred.impl.DeferredObject;
 
public class PipeExample {
 
	public static void main(String[] args) {
		IntStream.of(4, 1, 3).forEach(i -> {
			Multiplier m = new Multiplier(5);
			Promise<Long, Long, String> promise = m.promise().progress(System.out::println)
					.done((res) -> System.out.println("the result is: " + res));
			promise.then(new DonePipe<Long, Long, Exception, String>() {
				@Override
				public Promise<Long, Exception, String> pipeDone(Long result) {
					if (result < 50) {
						System.out.println("result < 50, that's too low -> error");
						return new DeferredObject<Long, Exception, String>().reject(new Exception("result too low"));
					}
					if (result < 130) {
						System.out.println("result < 130, doubling the value..");
						return new DeferredObject<Long, Exception, String>().resolve(result * 2);
					}
					return new DeferredObject<Long, Exception, String>().resolve(result);
				}
			}).fail((exception) -> System.err.println("error: " + exception.getMessage()))
					.done((result) -> System.out.println("pipe result: " + result));
			m.multiplyNTimes(i);
		});
	}
}

Running the code above in our IDE of choice or using Maven in the console should produce a similar output:

$ mvn exec:java -Dexec.mainClass=com.hascode.tutorial.PipeExample
[..]
status: 25%
status: 50%
status: 75%
status: 100%
the result is: 625
pipe result: 625
status: 100%
the result is: 5
result < 50, that's too low -> error
error: result too low
status: 33%
status: 66%
status: 100%
the result is: 125
result < 130, doubling the value..
pipe result: 250

Deferred Manager

The deferred manager is useful when we need to deal with asynchronous tasks using Callables or Runnables or as demonstrated below when we need to track the progress and the result of the computation of multiple Promise objects.

In the example below, we’re using a DeferredManager to observe three instances of our WebCrawler to display their progress and when everything is done, to display the results of each single promise:

package com.hascode.tutorial;
 
import java.util.List;
 
import org.jdeferred.DeferredManager;
import org.jdeferred.Promise;
import org.jdeferred.impl.DefaultDeferredManager;
 
public class DeferredManagerExample {
 
	public static void main(String[] args) {
		WebCrawler crawl1 = new WebCrawler();
		WebCrawler crawl2 = new WebCrawler();
		WebCrawler crawl3 = new WebCrawler();
 
		Promise<List<String>, String, Integer> promise1 = crawl1.promise();
		Promise<List<String>, String, Integer> promise2 = crawl2.promise();
		Promise<List<String>, String, Integer> promise3 = crawl3.promise();
 
		DeferredManager dm = new DefaultDeferredManager();
		dm.when(promise1, promise2, promise3).progress((p) -> {
			System.out.println(
					"progress-stats done: " + p.getDone() + " failed: " + p.getFail() + " total: " + p.getTotal());
		}).done((r) -> {
			System.out.println("All promises completed: ");
			r.forEach((o) -> {
				System.out.println("result: " + o.getResult());
			});
		});
 
		crawl1.crawl("https://www.hascode.com/");
		crawl2.crawl("http://www.java.net/");
		crawl3.crawl("http://www.micha-kops.com/");
	}
}

Running the code above in our IDE of choice or using Maven in the console should produce a similar output:

$ mvn exec:java -Dexec.mainClass=com.hascode.tutorial.DeferredManagerExample
[..]
initializing crawler
progress-stats done: 0 failed: 0 total: 3
fetching site content for url: https://www.hascode.com/
progress-stats done: 0 failed: 0 total: 3
filtering duplicate keywords
progress-stats done: 0 failed: 0 total: 3
storing keywords
progress-stats done: 1 failed: 0 total: 3
initializing crawler
progress-stats done: 1 failed: 0 total: 3
fetching site content for url: http://www.java.net/
progress-stats done: 1 failed: 0 total: 3
filtering duplicate keywords
progress-stats done: 1 failed: 0 total: 3
storing keywords
progress-stats done: 2 failed: 0 total: 3
initializing crawler
progress-stats done: 2 failed: 0 total: 3
fetching site content for url: http://www.micha-kops.com/
progress-stats done: 2 failed: 0 total: 3
filtering duplicate keywords
progress-stats done: 2 failed: 0 total: 3
storing keywords
progress-stats done: 3 failed: 0 total: 3
All promises completed:
result: [foo, bar, baz]
result: [foo, bar, baz]
result: [foo, bar, baz]

So as we can see, it’s nice to track the progress of our Promise objects here and to aggregate their result.

Deferred Manager with Executor Service

We may combine the DeferredManager with an ExecutorService for further thread control:

package com.hascode.tutorial;
 
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
import org.jdeferred.DeferredManager;
import org.jdeferred.Promise;
import org.jdeferred.impl.DefaultDeferredManager;
 
public class DeferredManagerWithExecutorServiceExample {
 
	public static void main(String[] args) {
		ExecutorService executorService = Executors.newCachedThreadPool();
		DeferredManager dm = new DefaultDeferredManager(executorService);
 
		WebCrawler crawl1 = new WebCrawler();
		 // [..]
	}
}

The result is identical to the last example above.

Tutorial Sources

Please feel free to download the tutorial sources from my Bitbucket repository, fork it there or clone it using Git:

git clone https://bitbucket.org/hascode/jdeferred-tutorial.git

Resources

Tags: , , , , , , , ,

3 Responses to “Using Deferred Objects and Promises with Java 8 and JDeferred”

  1. Saeid Says:

    Thanks…

  2. Micha Kops Says:

    You’re welcome! :)

  3. Sean K Says:

    Line 17 of PromiseFilterExample, the “then” invocation is ambiguous for the version of jderred jar library being used.

    [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-co mpile) on project promise: Compilation failure: Compilation failure:
    [ERROR] /C:/Users/203293/git/jdeferred-promise/src/main/java/com/cubic/cts/bo/PromiseFilterExample.ja va:[21,73] reference to then is ambiguous
    [ERROR] both method then(org.jdeferred.DoneFilter) in org.jdeferred.Pro mise and method then(org.jdeferred.DonePipe) in org.jdeferred .Promise match
    [ERROR] /C:/Users/sean/git/jdeferred-promise/src/main/java/com/sean/PromiseFilterExample.ja va:[21,78] incompatible types: inference variable D_OUT has incompatible bounds
    [ERROR] equality constraints: java.util.List
    [ERROR] lower bounds: void
    [ERROR] -> [Help 1]
    [ERROR]
    [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
    [ERROR] Re-run Maven using the -X switch to enable full debug logging.
    [ERROR]
    [ERROR] For more information about the errors and possible solutions, please read the following artic les:
    [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

    java version “1.8.0_131″
    Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
    Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)

    Apache Maven 3.5.0 (ff8f5e7444045639af65f6095c62210b5713f426; 2017-04-03T12:39:06-07:00)
    Maven home: C:\bin\maven-3.5.0
    Java version: 1.8.0_131, vendor: Oracle Corporation

Search
Categories