Trying continuation

  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

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.

Source code

4 thoughts on “Trying continuation

  1. Try this:

    @:async
    function fetchJson(url : String) : Either {
        switch (@await fetchText(url)) {
            case Left(v):
                return Either.Left(v);
    
            case Right(v): {
                return try {
                    Either.Right(cast Json.parse(v));
                } catch (e : Dynamic) {
                    Either.Left(Std.string(e));
                }
            }
        }
    }

Leave a Reply

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

*