- Why hxbolts?
- Trying promhx
- Trying thx.promise
- Trying task
- Trying continuation
- Trying async
- Trying hext-flow
- Finally, hxbolts
In this post I will try to use promhx v1.0.20. This library have impressive downloads count (6052) and github stars (91).
TL;DR – 🙁 Must keep in mind certain aspects of work, or you can spend a lot of time to solve the problem.
Let’s begin
I start with fetchText()
function. To make a promise from any async code, you must create a Deferred
instance and return promise()
of it.
private function fetchText(url : String) : Promise<String> {
var dp = new Deferred<String>();
var urlLoader = new URLLoader();
var onLoaderError = function(e : Event) : Void {
dp.throwError(e.type);
};
urlLoader.addEventListener(Event.COMPLETE, function(_) : Void {
dp.resolve(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) {
Timer.delay(function() : Void {
dp.throwError(Std.string(e));
}, 0);
}
return dp.promise();
}
One thing not in my taste – the Promise
class has both resolve()
and reject()
methods, but Deferred
has resolve()
and throwError()
. As Justin Donaldson said, that’s because Deferred
is used both for Promise
and Stream
.
There is another tricky thing about it. If you call throwError()
(or reject()
for Promise
) when there is any error handler, than error will not thrown, but error handlers called (and this is super mega cool). But in other case error will thrown (and this doesn’t work well for my case). As work-around I call throwError()
in next loop using Timer.delay()
.
function fetchJson(url : String) : Promise<DynamicExt> {
return fetchText(url).then(function(result : String) : DynamicExt {
return cast Json.parse(result);
});
}
That’s was easy. Just use then()
method to chain promises.
function fetchBitmapData(url : String) : Promise<BitmapData> {
var dp = new Deferred<BitmapData>();
var loader = new Loader();
var onLoaderError = function(e : Event) : Void {
dp.throwError(e.type);
};
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function(_) : Void {
dp.resolve((cast loader.content:Bitmap).bitmapData);
});
loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onLoaderError);
loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onLoaderError);
try {
loader.load(new URLRequest(url), new LoaderContext(true));
} catch (e : Dynamic) {
Timer.delay(function() : Void {
dp.throwError(Std.string(e));
}, 0);
}
return dp.promise();
}
It’s just like fetchText()
.
function sendApiRequest(method : String) : Promise<DynamicExt> {
return fetchJson('http://some.api/${method}').then(function(result : DynamicExt) : DynamicExt {
if (result["error"] != null) {
throw result["error"];
}
return result;
});
}
Again, that’s easy. You can safely throw errors and than catch them later unless you specify -DPromhxExposeErrors
. But you must keep in mind tricky things about error handling.
Finally:
function syncState() : Promise<Bool> {
return cast sendApiRequest("sync-state").pipe(function(result : DynamicExt) : Promise<Bool> {
var subPromises = new Array<Promise<Bool>>();
updateStateFromTheResponse(result);
if (result.exists("playerAvatarUrl")) {
subPromises.push(cast fetchBitmapData(result["playerAvatarUrl"].asString()).then(function(bmd : BitmapData) : Bool {
setPlayerAvatar(bmd);
return true;
}).errorThen(function(e : Dynamic) : Bool {
return false;
}));
}
if (result.exists("opponentAvatarUrl")) {
subPromises.push(cast fetchBitmapData(result["opponentAvatarUrl"].asString()).then(function(bmd : BitmapData) : Bool {
setOpponentAvatar(bmd);
return true;
}).errorThen(function(e : Dynamic) : Bool {
return false;
}));
}
return Promise.whenAll(subPromises).then(function(list : Array<Bool>) : Bool {
for (val in list) {
if (!val) {
return false;
}
}
return true;
});
}).errorThen(function(e : Dynamic) : Bool {
return false;
});
}
function doTheTask() : Void {
showLoader();
syncState().then(function(success : Bool) : Void {
hideLoader();
if (success) {
onTaskSuccessed();
} else {
showErrorPopup();
}
});
}
That’s look easy, but in reality I spent several hours trying different combinations, looking into source code on github and experimenting to make it working.
Initially (when code looks different), I forgot to use pipe()
instead of then()
. Code compiles without any errors, but when I run test app, I got Type Coercion failed: cannot convert promhx::Promise to Array
. Shame on me, but I lost older code 🙁 With current version of code everything works well. In any case, in my humble opinion compiler errors is better than runtime errors.
Next thing seemed very simple, but in fact turned out to be very tricky. Initially, I use only one error handler in doTheTask()
function. I plan to call onTaskSuccessed()
in then()
and showErrorPopup()
in catchError()
. I think that it should work, but when I run the app, some of test failed randomly. For a long time I coundn’t understand where is the problem. In face, it turned out than when error happened, catchError()
called immediately. It may work for most of cases, but just didn’t work for my case. Example situation:
- Start to load player avatar and return promise
- Start to load opponent avatar and return promise
- return
Promise.whenAll()
of these promises - Unfortunately, opponent avatar was not loaded and error thrown
catchError()
handler called- After it player avatar loaded
I’m fine with situation when player avatar is loaded and opponent avatar failed. But I don’t expect that something would be called after error handler.
I tried different solutions, but the only one that works is not to use global error handler at all. Instead of I return Bool
, where true
means success and false
means that error occurred. Not very intuitive for me.
Pros
- Impressive downloads count and github stars;
- Although I don’t use this, comes with classes for FRP.
Cons
- In some really rare cases stong typing didn’t work;
- Tricky error handling, you must keep it in your mind.
In my humble opinion
- Code is cryptic a little;
reject
vsthrowError()
. There is a good reason why this is done, but it just not in my taste.
Conclusion
It works, but you must keep in mind certain things. Also in some very rare cases strong typing didn’t work.
2 thoughts on “Trying promhx”