Saturday, March 3, 2012

Errors from the Future (of Dart)

‹prev | My Chain | next›

Last night I cleaned up a bit of callback hell in my Dart-based Hipster MVC library. Today, I am going to strike out off of the happy path. I had not begun to deal with errors in my callback version of the MVC library. Let's see how easy it is to handle the unexpected with Futures and Completers.

So I modify the express.js backend to always throw an error when POSTing:
app.post('/comics', function(req, res) {
  res.send(409);
}
Then I modify the frontend code to handle errors:
class HipsterSync {
  // ...
  static Future<Dynamic> _defaultSync(method, model) {
    var request = new XMLHttpRequest(),
        completer = new Completer();

    request.
      on.
      error.
      add((event) {
        print("Something bad happened");
      });

    request.
      on.
      load.
      add((event) {
        var req = event.target,
            json = JSON.parse(req.responseText);
        completer.complete(json);
      });

    request.open(method, model.url, true);

    // POST and PUT HTTP request bodies if necessary
    if (method == 'post' || method == 'put') {
      request.setRequestHeader('Content-type', 'application/json');
      request.send(JSON.stringify(model.attributes));
    }
    else {
      request.send();
    }

    return completer.future;
  }
}
I may need to break that up -- possibly extracting the Ajax callbacks into their own methods. For now, I will focus on getting it to work. When I try to POST from my comic book application, however, I get:


So Dart seemingly does not see this as an error. The stacktrace is because my normal "load" listener is handling this case. Bah!

I remove my "error" listener and add the error handling directly in the normal "load" handler:
  static Future<Dynamic> _defaultSync(method, model) {
    // ...
    request.
      on.
      load.
      add((event) {
        var req = event.target;

        if (req.status > 299) _handleError(req, completer);

        var json = JSON.parse(req.responseText);
        completer.complete(json);
      });
    // ...
  }

  static void _handleError(request, completer) {
    completer.completeException(new Exception("That ain't gonna work: ${request.status}"));
  }
That does the trick, as I now see my "That ain't gonna work error" at the top of the stacktrace in the Dart console:


To get rid of this, I ought to be able to define a handleException method on the Future returned from save(). So I change this:
  create(attrs) {
    _buildModel(attrs).
      save().
      then((saved_model) {
        this.add(saved_model);
      });
  }
To This:
  create(attrs) {
    Future after_save = _buildModel(attrs).save();

    after_save.
      then((saved_model) {
        this.add(saved_model);
      });

    after_save.
      handleException((e) {
        print("Exception handled: ${e.type}");
        return true;
      });
  }
The return true statement at the end of my handleException function is supposed to mark this exception as handled. But reality is not matching the spec in this case. Instead, I continue to see the exception bubbling all the way up to the user.


Ugh. Straying from the happy path in Dart is not working well for me tonight. So, in the end, I have to settle for catching the HipsterSync exception directly. That is not a huge loss, but the exception types could become a bit tricky (depending on if the sync is for a collection or a model). And really, that handleException() method should work properly. Well, I'm off to the Dart bug tracking system....

Update: this seems to work in simpler cases. That also works in Dartium. So somewhere between my complex multi-library completer/future and this simple case, things break down. I will keep investigating tomorrow.


Day #314

No comments:

Post a Comment