Fluent asynchronous API 4: Nope, Q won’t work

As I said at the end of the previous post, I really thought that a promise library, Q in particular, would help, and would enable me to get rid of my helper library. It turns out, after I tried to make it work, that it won’t. The reasons have to do , among other things, with how hard it is to chain a new asynchronous method onto an existing promise, a strange limitation of those libraries. Well, in fact you can very well create a new promise that combines the old one and a new one for the new asynchronous operation, but that would mean managing a list of promises instead of managing the todo list of callbacks I have in my own helper. In other words, I would still have to maintain a helper that would be, maybe, marginally simpler than what I have now, except that it would require a dependency on another much more complex library that I didn’t write.

I’m actually quite happy with how much I can do with such a small library. The code is not exactly trivial, but my requirements are well fulfilled, and the public API looks nice and tidy.

To conclude this series of posts, I’ll publish below the only code I haven’t shown yet: the test cases, which I think do a fine job of showing the different ways the library can be used.

The library is now available as a NPM package:

https://www.npmjs.org/package/flasync

npm install flasync --save

Please let me know in the comments if this was useful, or if you have any suggestions on what can be improved, criticism of the design, errors I may have made, etc.

'use strict';
var expect = require('chai').expect;
var flasync = require('../lib/flasync');

describe('Flasync Fluent Async API helper', function() {
// Build an API using the library
var Api = function Api(output) {
var self = this;
this.output = output || [];
flasync(this);
// It has one synchronous method
this.writeSync = this.asyncify(function(text) {
this.output.push(text);
return this;
});
// And one asynchronous method
this.write = this.async(function(text, next) {
process.nextTick(function () {
self.output.push(text);
next();
});
return this;
});
};

it('behaves as a synchronous API as long as only synchronous methods are called', function() {
var api = new Api();

api
.writeSync('foo')
.writeSync('bar')
.writeSync('baz');

expect(api.output).to.deep.equal(['foo', 'bar', 'baz']);
});

it('preserves call order even for synchronous methods called after an asynchronous one', function(done) {
var api = new Api();

api
.writeSync('foo')
.write('bar')
.writeSync('baz')
.then(function() {
expect(api.output).to.deep.equal(['foo', 'bar', 'baz']);
done();
});
});

it('executes asynchronous continuations in order', function(done) {
var api = new Api();

api
.write('foo')
.then(function(next) {
api.output.push('bar');
next();
})
.then(function(next) {
api.output.push('baz');
next();
})
.then(function() {
expect(api.output).to.deep.equal(['foo', 'bar', 'baz']);
done();
});
});

it('can nest async calls', function(done) {
var api = new Api();

api
.write('foo')
.then(function(next) {
api
.writeSync('bar')
.write('baz')
.then(function() {
expect(api.output).to.deep.equal(['foo', 'bar', 'baz']);
done();
});
next();
});
});

it('can handle exceptions and suspend execution', function(done) {
var api = new Api();
var hit = false;

api
.onError(function(err) {
expect(hit).to.be.false;
done();
})
.then(function(next) {
throw new Error('oops');
})
.then(function(next) {
hit = true;
});
});

it('lets exceptions through if no error handler is defined', function() {
var api = new Api();

expect(function() {
api
.then(function(next) {
throw new Error('oops');
})
})
.to.throw('oops');
});
});

No Comments