Yesterday, I failed miserably to get Dart to work with IndexedDB in Dartium. It was a pretty disappointing outing all around, but not a complete waste. I eventually came across a nice example of Dart IndexedDB written by Seth Ladd. It only works when compiled into Javascript, so it was not directly useful to me. But it did get me to thinking about my callback ways.
Coming from a very functional Javascript / node.js background, I am perfectly comfortable slinging callbacks about. In my Hipster MVC library, for example, I supply a callback when I save individual models:
create(attrs) {
var new_model = modelMaker(attrs);
new_model.collection = this;
new_model.save(callback:(event) {
this.add(new_model);
});
}
When I read through Seth's IndexedDB code, it struck me how much he relies on Futures. This, naturally enough, made me feel all inadequate in my Dart coding. Well today, I shall be inadequate no more! Well... at least in this area.Looking into HipsterModel, I see that my save is awash in callback... stuff:
class HipsterModel { // ... save([callback]) { HipsterSync.call('post', this, options: { 'onLoad': (attrs) { attributes = attrs; var event = new ModelEvent('save', this); on.load.dispatch(event); if (callback != null) callback(event); } }); } // ... }Instead of this approach, I declare that
save()
returns a Future
. In the method body, I need a Completer, whose future
I can return:
Future<HipsterModel> save() {
Completer completer = new Completer();
HipsterSync.call('post', this, options: {
'onLoad': (attrs) {
attributes = attrs;
var event = new ModelEvent('save', this);
on.load.dispatch(event);
completer.complete(this);
}
});
return completer.future;
}
The future is not invoked until the Completer
completes, which happens inside the onLoad
callback (say, I can probably get rid of that too). Back in the Collection class, I now need a then()
method for the returned Future
:class HipsterModel {
// ...
create(attrs) {
var new_model = modelMaker(attrs);
new_model.collection = this;
new_model.
save().
then((saved_model) {
this.add(new_model);
});
}
// ...
}
Ooh! I like that. It reads a heck of a lot better than my callback
parameter. Here, I can read that I save my new model, then add it to the current collection.A quick sanity check verifies everything still works:
Back in
HipsterModel#save
, I am still invoking HipsterSync.call()
with a callback:class HipsterModel { // ... Future<HipsterModel> save() { Completer completer = new Completer(); HipsterSync.call('post', this, options: { 'onLoad': (attrs) { attributes = attrs; var event = new ModelEvent('save', this); on.load.dispatch(event); completer.complete(this); } }); return completer.future; } // ... }If I replace the callback in
HipsterSync
with a Future, the callback becomes:class HipsterModel { // ... Future<HipsterModel> save() { Completer completer = new Completer(); HipsterSync. call('post', this). then((attrs) { this.attributes = attrs; on.load.dispatch(new ModelEvent('save', this)); completer.complete(this); }); return completer.future; } // ... }That is so much nicer to read. I am calling HipsterSync, telling it to POST the model. Then, I take the attributes returned from the sync and assign them to the model's attributes, dispatch any events, and, finally, complete the
save()
completer from earlier.That is much cleaner than the callback approach. I may have to start using custom-built Futures in Javascript. One thing missing from this is exceptions—I have a lot of happy path going on. Fortunately, there are exception facilities built into Futures. I will take a look at those tomorrow.
Day #313
No comments:
Post a Comment