Pesky hidden 'try' is catching all my errors, leading to silent fail

I’m using a haxe library called thx.Promise.
I’ve been experiencing a strange problem which I suspect is due to my ignorance rather than a bug or bad design.
I’ve written a very simple (if pointless) example of an asset loading system using promises.

var promises:Array<Promise<thx.Nil>> = [
	Promise.create(function(resolve:thx.Nil->Void, reject:thx.Error->Void) {
		var loader = Assets.loadLibrary("assets");
		loader.onComplete(function(_) {
			resolve(thx.Nil.nil);
		});
		loader.onError(function(e){
			reject(thx.Error.fromDynamic(e));
		});
	})
];

Promise.afterAll(promises).success(function(_){
	start();
});

function start() {
	null.property = 1;
}

When all the promises are resolved I run a function called start().
In the background without my knowing, the callback is wrapped in a try {} catch, which means if I encounter an error in start(), it get’s ignored.
The only way I can resolve this is by wrapping my start call with another try catch of my own:

Promise.afterAll(promises).success(function(_){
	try {
		start();
	} catch (e:Error) {
		trace(e);
	}
});

While it works, it strikes me as an ugly solution to a very simple problem, and I suspect there’s a more elegant solution to this admittedly very minor problem.

I found the try/catch block in question:

It was incredibly frustrating to dig further, given the inconsistent naming, not to mention all the wrapping and unwrapping that gets done.

Anyway, the “reject” function is defined here, meaning it passes the error to the “cb” function. Looking at Future.create(), it turns out that cb is future.setState(), so the state will either be set to a value or an error.

But when will that happen? Future.create() immediately calls this callback function, which immediately calls callback, but the two cb calls are wrapped in functions themselves, so they aren’t called immediately. It all depends when callback calls the arguments it was passed.

As previously mentioned, callback is this anonymous function, which defers things using the then() function.


At this point, it’s worth distinguishing between all the Promise objects floating around.

Promise 0: The one from Promise.create(), stored in your array.
Promise 1: The one created from your array using Promise.afterAll().
Promise 1.1: A dummy Promise created during afterAll(). (There’s only one because your array has only one item.)
Promise 2: The one created from Promise 1 using success().

As expected, Promise 1 is the one that the then() function is waiting for.


Now it’s time to figure out what happens when your assets finish loading.

  1. The loader calls your onComplete function.
  2. Your onComplete function calls resolve(). (resolve() is an anonymous function that passes its argument to setState().)
  3. resolve() passes thx.Nil.nil to Promise 0’s setState() function.
  4. Promise 0 dispatches a complete “event.”
  5. This event filters through Promise 1’s chain.
  6. Promise 1 dispatches a complete “event.”
  7. This anonymous function is finally called. Since the chain in step 5 was successful, the argument r is Right(v), where v is an array of the results.
  8. Your success listener is called, from within a try block.
  9. Your function calls start().
  10. start() throws an error.
  11. The error is caught.
  12. The catch block calls reject(), passing the error.
  13. reject() is this anonymous function, and you can’t override it.

Having done all that work, I can tell that the solution is actually not that hard. However, I cannot in good conscience support this library, and I’d much rather encourage you to switch to a better one.

Even though it stopped being updated a while ago, Promhx has better documentation and code structure, and it’s had more effort put into it overall. Like thx.promise, It catches your errors. Unlike thx.promise, Promhx automatically prints the errors it catches, along with any extra information it can gather.

1 Like

You can also use Lime Promises, I’m open to suggestions if important features are missing for you :slight_smile:

For example:

project.xml

<assets path="Assets/stuff" library="stuff" />

Haxe

var future = Assets.loadLibrary ("stuff"); 
future.onComplete (function (library) {
    var asset = library.getImage ("image.png");
    // or
    var asset = Assets.getBitmapData ("stuff:image.png");
    
    ...
   
});
2 Likes

Also a good idea. Lime doesn’t have inline comments like Promhx does, but the code is easy enough to follow that comments aren’t necessary.

However, there’s no function that address Tom’s use case. Fortunately, it isn’t hard to work around it:

var libraryNames = ["assets", "swfassets", "moreassets"];
var completeCount = 0;

for(name in libraryNames) {
	var future = Assets.loadLibrary(name);
	
	future.onComplete(function(_) {
		completeCount++;
		
		if(completeCount >= libraryNames.length) {
			start();
		}
	});
}

Wow. I really appreciate the effort here. I wasn’t expecting any replies. Thank you!

I tried promhx briefly but for some reason I saw Deferred (a term I’m not that familiar with) and got scared, so went with thx.promise instead :slight_smile:

I’ve just tried switching out thx.promise for promhx and it seems to work a bit more intuitively than I first thought.

One problem which niggles me though - when I encounter an error in a callback, it holds onto it for a brief moment until a timer tick event, so in HaxeDevelop, I lose all my stack information. However it does kindly print the error in the console for me along with a line number and function name.
It would be good if when you’re ready to go back to the normal synchronous world, if you could politely shake hands with a promise and say - “Guys, that was terrific, but your work is done. I’ve got this.”

Unless I’m mistaken in its usage, thx.promise has a ‘createUnsafe’ function which allows you to do this, but I couldn’t work out how to implement it after calling afterAll().

Not necessary but this is the same code but rewritten slightly to suit promhx:

private function loadLibrary():Promise<Bool>
{
	var dp = new Deferred<Bool>();
	var loader = Assets.loadLibrary("assets");
	loader.onComplete(function(_) {
		dp.resolve(true);
	});
	loader.onError(function(e){
		dp.throwError(e);
	});
	return dp.promise();
}

private function init(event:Event)
{
	removeEventListener(Event.ADDED_TO_STAGE, init);
	
	Promise.whenAll([loadLibrary(), otherPromise, anotherPromise]).then(function(_){
		start();
	});
}

function start() 
{
	null.property = 1;
}

So I was going to offer some advice on how to implement it, but when I went to figure out which line you should edit, I found that it was already there. All you have to do is compile with <haxedef name="PromhxExposeErrors" />.

Excellent! It works just how I want it to now :slight_smile:
Is there any reason why I wouldn’t want to use PromhxExposeErrors? I just don’t understand why this wouldn’t be the default behaviour.

The library provides a couple other options for error handling. You could use catchError() to register an error listener. Errors propagate down the chain of Promises unless caught, so if you do this on the final Promise, you could you listen for errors anywhere in the chain.

There’s also errorThen(), which lets you set a default value in case of an error. Once an error is handled this way, any following Promises can continue as normal.

But if all you want is the error message and stack trace, go ahead and expose the errors.