- Why hxbolts?
- Trying promhx
- Trying thx.promise
- Trying task
- Trying continuation
- Trying async
- Trying hext-flow
- Finally, hxbolts
This time I’ll use a library that uses a different approach. Library called “continuation”, and installed 1345 times. I will use version 1.3.2.
TL;DR – 🙁 Looks promising, but usage of try
/ catch
blocks inside @:async
functions is tricky.
Let’s begin
This library uses haxe macro magic and makes it possible to use C#-like async / await. At first, you need to transform function with callbacks in a function suitable to use with @await
. Fortunately, you shoudn’t do anything special for that. Any function which accepts callback as last argument is suitable for continuation.
function fetchText(url : String, callback : Either<String, String> -> Void) : Void {
var urlLoader = new URLLoader();
var onLoaderError = function(e : Event) : Void {
callback(Either.Left(e.type));
};
urlLoader.addEventListener(Event.COMPLETE, function(_) : Void {
callback(Either.Right(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) {
callback(Either.Left(Std.string(e)));
}
}
I must use haxe.ds.Either
, because there is no way to “transfer” exception into callback. Than a bit of magic:
@:async
function fetchJson(url : String) : Either<String, DynamicExt> {
switch (@await fetchText(url)) {
case Left(v):
return Either.Left(v);
case Right(v): {
try {
return Either.Right(cast Json.parse(v));
} catch (e : Dynamic) {
return Either.Left(Std.string(e));
}
}
}
}
Wow? Nope. You’ll get a compilation error. Library doesn’t work properly when @:async
function contains try
/ catch
blocks. But if you rewrite it like this …
function fetchJsonTryCatcher(v : DynamicExt) : Either<String, DynamicExt> {
try {
return Either.Right(cast Json.parse(v));
} catch (e : Dynamic) {
return Either.Left(Std.string(e));
}
}
@:async
private function fetchJson(url : String) : Either<String, DynamicExt> {
switch (@await fetchText(url)) {
case Left(v):
return Either.Left(v);
case Right(v): {
return fetchJsonTryCatcher(v);
}
}
}
… everything starts to work. UPD from 2015-08-10: There is better work-around for this, see discussion in comments.
Next two functions:
@:async
function sendApiRequest(method : String) : Either<String, DynamicExt> {
switch (@await fetchJson('http://some.api/${method}')) {
case Left(v):
return Either.Left(v);
case Right(v):
if (v["error"] != null) {
return Either.Left(v["error"].asString());
} else {
return Either.Right(v);
}
}
}
@:async
function syncState() : Bool {
switch (@await sendApiRequest("sync-state")) {
case Left(v):
return false;
case Right(v): {
var result = true;
updateStateFromTheResponse(v);
if (v.exists("playerAvatarUrl")) {
switch (@await fetchBitmapData(v["playerAvatarUrl"])) {
case Left(v):
result = false;
case Right(v):
setPlayerAvatar(v);
}
}
if (v.exists("opponentAvatarUrl")) {
switch (@await fetchBitmapData(v["opponentAvatarUrl"])) {
case Left(v):
result = false;
case Right(v):
setOpponentAvatar(v);
}
}
return result;
}
}
}
Finally, to use @:async
function from non-async you should use it just like any other function with callback:
public function doTheTask() : Void {
showLoader();
syncState(function(result : Bool) : Void {
hideLoader();
if (result) {
onTaskSuccessed();
} else {
showErrorPopup();
}
});
}
Pros
- An interesting approach.
- Async code looks like non-async.
Cons
- No way to “transfer” exception from callback-function to async-function.
- Bug with
try
/catch
blocks. - I’m not sure (because of bug with
try
/catch
), but it seems that throwing exceptions inside async functions is not supported either.
Conclusion
Very promising, but usage of try
/ catch
blocks inside @:async
functions is tricky.
4 thoughts on “Trying continuation”