- Why hxbolts?
- Trying promhx
- Trying thx.promise
- Trying task
- Trying continuation
- Trying async
- Trying hext-flow
- Finally, hxbolts
And finally, I’ll make the same task using hxbolts (few words about – it is pure haxe port of java library Bolts, which itself seems to be inspired by Task Parallel Library from dotNET).
TL;DR – 🙂 Strongly typed, easy to use. Without all this async / await magic, but predictable and well tested.
Let’s begin
Again and again I begin with old-good fetchText()
function. In this library promise called Task
and handle to promise called TaskCompletionSource
(not the best naming ever, but works). I don’t need to use any hacks with Timer.delay()
– it just works as expected.
function fetchText(url : String) : Task<String> {
var tcs = new TaskCompletionSource<String>();
var urlLoader = new URLLoader();
var onLoaderError = function(e : Event) : Void {
tcs.setError(e.type);
};
urlLoader.addEventListener(Event.COMPLETE, function(_) : Void {
tcs.setResult(Std.string(urlLoader.data));
});
urlLoader.addEventListener(IOErrorEvent.IO_ERROR, onLoaderError);
urlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onLoaderError);
try {
urlLoader.dataFormat = URLLoaderDataFormat.TEXT;
urlLoader.load(new URLRequest(url));
} catch (e : Dynamic) {
tcs.setError(Std.string(e));
}
return tcs.task;
}
Rest is easy – just use onSuccess()
to chain tasks. One note – onSuccess()
accepts not result of previous operation, but rather task of previous operation. It confusing a little, but this is done to be consistent with continueWith()
function.
Library will take care about any thrown exceptions, so I don’t need to do anything special.
function fetchJson(url : String) : Task<DynamicExt> {
return fetchText(url).onSuccess(function(task : Task<String>) : DynamicExt {
return cast Json.parse(task.result);
});
}
function sendApiRequest(method : String) : Task<DynamicExt> {
return fetchJson('http://some.api/${method}').onSuccess(function(task : Task<DynamicExt>) : DynamicExt {
if (task.result["error"] != null) {
throw task.result["error"];
}
return task.result;
});
}
Next function is syncState()
. Task.whenAll()
will wait when all sub-tasks were completed (either resolved or rejected), so I don’t need to do anything special to deal with cases when one task still running, but other already failed. Everything just works as expected.
function syncState() : Task<Void> {
return sendApiRequest("sync-state").onSuccessTask(function(task : Task<DynamicExt>) : Task<Void> {
var subTasks = new Array<Task<Void>>();
updateStateFromTheResponse(task.result);
if (task.result.exists("playerAvatarUrl")) {
subTasks.push(fetchBitmapData(task.result["playerAvatarUrl"].asString()).onSuccess(function(t : Task<BitmapData>) : Void {
setPlayerAvatar(t.result);
}));
}
if (task.result.exists("opponentAvatarUrl")) {
subTasks.push(fetchBitmapData(task.result["opponentAvatarUrl"].asString()).onSuccess(function(t : Task<BitmapData>) : Void {
setOpponentAvatar(t.result);
}));
}
return Task.whenAll(subTasks);
});
}
Finally:
function doTheTask() : Void {
showLoader();
syncState().continueWith(function(task : Task<Void>) : Void {
hideLoader();
if (task.isSuccessed) {
onTaskSuccessed();
} else {
showErrorPopup();
}
});
}
Pros
As an author of the port, I can not be objective, so forgive me laudatory odes… Ok, will be more serious.
- Everything is strongly typed.
- Support for exceptions.
- You don’t need to write workarounds to achieve result – it just works as expected.
- Everything it tested with unit tests.
- Although I don’t use this now, can execute tasks on different threads, and everything should be thread-safe.
Cons
- Not the best method naming ever –
Task
(instead ofPromise
),TaskCompletionSource
,onSuccess
(instead ofthen
), etc. - Unit tests use
Math.random()
inside, which makes them less predictable.
In my humble opinion
- Would be nice to have support for async / await. Maybe in version 4.0…
Conclusion
In my hubmle opinion, a worthy competitor to other libraries. Strongly typed, predictable and well tested.