Trying promhx

  1. Why hxbolts?
  2. Trying promhx
  3. Trying thx.promise
  4. Trying task
  5. Trying continuation
  6. Trying async
  7. Trying hext-flow
  8. 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 vs throwError(). 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.

Source code

2 thoughts on “Trying promhx

  1. Sorry you’re disappointed with the library, although I’m a bit disappointed as well. If you’re doing a write up like this you want to reach out to the authors of the libraries if you’re unclear on the conventions or rationales for why they do things a certain way.

    So, I can address your comments here:

    “One thing I don’t understand – the Promise class has both resolve() and reject() methods, but Deferred has only resolve(), and you must use throwError() if you want to reject it. The name is confusing.”

    Only the Promise class has reject, the Stream class doesn’t. For promises, the value should resolve only once. Rejecting it puts it in a permanent state of error. The “throwError” method is a mechanism that works on both via the Deferred interface. It is a more general interface, so it uses the notion of a thrown error that is common to both.

    “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. In other case error will thrown. As work-around I call throwError() in next loop using Timer.delay().”

    This shouldn’t be tricky, in fact it’s a first class feature I describe in the intro. Asynchronous error handling is difficult, since try/catch blocks can not guarantee that you capture all the errors. The error handling functionality of promhx lets you delegate error handling across an entire chain of asynchronous operations, which is very powerful if you do a lot of asynchronous programming.

    In the next couple of paragraphs, it seems you really get into the weeds with error handling. This may be because you’re using -DPromhxExposeErrors, which is a debugging utility, not something intended to be used for production code. That -D flag helps you catch very simple errors that have nothing to do with asynchronous problems (network timeouts, etc.)

    You mention a type coercion error, and I’m not certain if that happens in the code you post here, or in some other version. However, promhx relies on static typing throughout the code, including checking for generic types via pipe/then, as well as through the macro whenAll method. Are you using dynamic in your code somewhere? I can’t understand how that is happening for you.

    As a library author, it’s very frustrating to see someone misuse one of your libraries and then claim that it’s deficient.

    • At first, I want to apologize for too aggressive post style, especially because other reviews are less aggressive. I’ll rewrite post in less aggressive manner soon.

      At second, I’d like to explain why I wrote these posts: I have real task (from my client) for real game. I tried a lot of libraries, and finally decide to write my own. Than I wanted to share my knowledge.

      How try each library: I carefully read readme files, wiki’s, examples, demos and unit tests (if available). Only after that I started to write my own code. So it is not I-didnt-read-manual-but-write-post style.

      … The “throwError” method is a mechanism that works on both via the Deferred interface. It is a more general interface, so it uses the notion of a thrown error that is common to both.

      In my opinion that should be mentioned in readme.

      The error handling functionality of promhx lets you delegate error handling across an entire chain of asynchronous operations, which is very powerful if you do a lot of asynchronous programming.

      I’m talking exactly about it. In my humble opinion it should delegate error handling across an entire chain, but in certain cases it didn’t work: https://github.com/restorer/why-hxbolts/blob/master/source/org/sample/TestPromhx.hx#L53

      This may be because you’re using -DPromhxExposeErrors, which is a debugging utility, not something intended to be used for production code

      Nope. Moreover I write (in positive manner) about this flag it in this post: “You can safely throw errors and than catch them later unless you specify -DPromhxExposeErrors.”

      You mention a type coercion error, and I’m not certain if that happens in the code you post here, or in some other version. However, promhx relies on static typing throughout the code, including checking for generic types via pipe/then, as well as through the macro whenAll method. Are you using dynamic in your code somewhere? I can’t understand how that is happening for you.

      It happened on older code version, and I can’t reproduce this bug with current code. Obliviously, there was my bug in older code – I tried to use then instead of pipe, probably there was more my errors, maybe with types or something. In any case, in my humble opinion compiler error is better than runtime error.

      Shame on me, but I lost older code version.

      As a library author, it’s very frustrating to see someone misuse one of your libraries and then claim that it’s deficient.

      Again, sorry for too aggressive post style. I didn’t mean that your library is deficient. Moreover I found a way (with a few hacks) to solve my task using promhx, which means that library is usable.

      And I don’t think that I misused it in any way. Before looking at library source code, I tried everything mentioned in readme; before adding hacks, I looked into source code, etc.

      I restored some older variants with exact errors: https://github.com/restorer/why-hxbolts/blob/master/source/org/sample/TestPromhx.hx

Leave a Reply

Your email address will not be published. Required fields are marked *

*