Trying hext-flow

  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

Today I will try to use hext-flow. Unfortunately version from haxelib don’t work anymore, so I used version from github instead (both hext-flow and hext-core).

TL;DR – 🙁 Has no ability to chain promises, resolved and rejected values must be of the same type, promise can be resolved outside of creator function.

Let’s begin

As usual, I’m start with fetchText() function. You should create a Promise instance, and reject() or resolve() it later. It is not safe, because any code outside of this function can resolve or reject promise by itself.

Other tricky thing – you can’t return already resolved or rejected promise, because exception will be thrown if you try to add resolved or rejected handlers on completed promise. As work-around I reject promise on next tick using Timer.delay().

One more tricky thing – resolved and rejected values must be of the same type. That’s strange, because typical pattern is to use Dynamic for rejected value and some type (strongly typed using generics) on resolved value. In my case I don’t need exact rejected value, so I just pass null. But if I will need it, I probably will use haxe.ds.Either.

function fetchText(url : String) : Promise<String> {
    var resultPromise = new Promise<String>();
    var urlLoader = new URLLoader();

    var onLoaderError = function(e : Event) : Void {
        resultPromise.reject(null);
    };

    urlLoader.addEventListener(Event.COMPLETE, function(_) : Void {
        resultPromise.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 {
            resultPromise.reject(null);
        }, 0);
    }

    return resultPromise;
}

This library has no ability to chain promises, so I must do it manually:

function fetchJson(url : String) : Promise<DynamicExt> {
    var resultPromise = new Promise<DynamicExt>();
    var fetchTextPromise = fetchText(url);

    fetchTextPromise.resolved(function(v : String) : Void {
        try {
            resultPromise.resolve(cast Json.parse(v));
        } catch (e : Dynamic) {
            resultPromise.reject(null);
        }
    });

    fetchTextPromise.rejected(function(_) : Void {
        resultPromise.reject(null);
    });

    return resultPromise;
}

function sendApiRequest(method : String) : Promise<DynamicExt> {
    var resultPromise = new Promise<DynamicExt>();
    var fetchJsonPromise = fetchJson('http://some.api/${method}');

    fetchJsonPromise.resolved(function(v : DynamicExt) : Void {
        if (v["error"] != null) {
            resultPromise.reject(null);
        } else {
            resultPromise.resolve(v);
        }
    });

    fetchJsonPromise.rejected(function(_) : Void {
        resultPromise.reject(null);
    });

    return resultPromise;
}

Library has Promise.when() method, which accepts array of promises, and returns promise, that will be resolved when all sub-promises will be resolved. However this method will reject result promise as soon, as one of sub-promises rejects (just like other libraries I tried). That approach just not work in my case (but can work in other cases). As work-around I create success variable to indicate success / failure status.

Other tricky thing – when empty array passed to Promise.when(), exception will be thrown. Yet another work-around…

And one more thing – due to strict typing Promise<Void> can’t be used, so I create Promise<Dynamic> instead. By the way, I think that this is good thing.

function syncState() : Promise<Dynamic> {
    var sendApiRequestPromise = sendApiRequest("sync-state");
    var resultPromise = new Promise<Dynamic>();

    sendApiRequestPromise.resolved(function(v : DynamicExt) : Void {
        updateStateFromTheResponse(v);

        var subPromises : Array<Promise<Dynamic>> = [];
        var success = true;

        if (v.exists("playerAvatarUrl")) {
            var subPromise = new Promise<Dynamic>();
            var fetchBitmapDataPromise = fetchBitmapData(v["playerAvatarUrl"].asString());

            fetchBitmapDataPromise.resolved(function(v : BitmapData) : Void {
                setPlayerAvatar(v);
                subPromise.resolve(null);
            });

            fetchBitmapDataPromise.rejected(function(_) : Void {
                success = false;
                subPromise.resolve(null);
            });

            subPromises.push(subPromise);
        }

        if (v.exists("opponentAvatarUrl")) {
            var subPromise = new Promise<Dynamic>();
            var fetchBitmapDataPromise = fetchBitmapData(v["opponentAvatarUrl"].asString());

            fetchBitmapDataPromise.resolved(function(v : BitmapData) : Void {
                setOpponentAvatar(v);
                subPromise.resolve(null);
            });

            fetchBitmapDataPromise.rejected(function(_) : Void {
                success = false;
                subPromise.resolve(null);
            });

            subPromises.push(subPromise);
        }

        if (subPromises.length != 0) {
            Promise.when(subPromises).resolved(function(_) : Void {
                if (success) {
                    resultPromise.resolve(null);
                } else {
                    resultPromise.reject(null);
                }
            });
        } else {
            resultPromise.resolve(null);
        }
    });

    sendApiRequestPromise.rejected(function(_) : Void {
        resultPromise.reject(null);
    });

    return resultPromise;
}

Finally:

function doTheTask() : Void {
    showLoader();
    var syncStatePromise = syncState();

    syncStatePromise.done(function(_) : Void {
        hideLoader();

        if (syncStatePromise.isResolved()) {
            onTaskSuccessed();
        } else {
            showErrorPopup();
        }
    });
}

Pros

  • Really strict typing.

Cons

  • Has no ability to chain promises.
  • Resolved and rejected values must be of the same type.
  • Promise can be resolved outside of creator function.

Not in my taste

  • You can’t return already resolved or rejected promise.
  • When empty array passed to Promise.when(), exception will be thrown.
  • Promise.when() will reject result promise as soon, as one of sub-promises rejects.

Conclusion

Indeed, it can simplify asynchronous tasks, but I think you should give a chance to other libraries on haxelib.

Source code

Leave a Reply

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

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.