Fluent asynchronous API 6: recursive calls

This can never work:

api
.then(function thenRegisterNextTick(next) {
process.nextTick(function onTick() {
api.finally(next);
});
})
.finally(done);

The “next” callback function gets called from a promise the execution of which depends on the completion of the “then” call. This is a deadlock, albeit a subtle one.

The problem is that if the API is recursive in any way, you’ll want code in asynchronous methods to use the API, not unlike the sample above, but potentially in an even less obvious way.

The API that I’m building, and the reason why I built the “flasync” helper mix-in in the first place, is an HTML rendering API. It has methods to render tags, add stylesheets, etc. It also has a higher-level, asynchronous method to render complex “shapes” that will render their own tags. There are potentially many sorts of shapes, each with their own rendering logic. In other words, there is unknown code that will use the API from an asynchronous method call.

The way out of this problem is that from within the asynchronous call, a local queue should be used, instead of the one that the call was queued on. It’s like we have local asynchronous life cycles within life cycles. The easiest way to do this is to new up another instance of the API and pass it as a parameter, and that’s exactly what I’m doing in my shape rendering method.

Following the same idea, it’s possible to rewrite the failing example above without the deadlock:

api
.then(function thenRegisterNextTick(next) {
var innerApi = new Api();
process.nextTick(function onTick() {
innerApi.finally(next);
});
})
.finally(done);

This is a small additional constraint for the API author: if a method must asynchronously call code that may use the API, a new instance of the API must be passed instead of “this”. For the downstream code, as well as for the client code, nothing changes, and everything works as expected.

I’m really liking how this has turned out. Problems like the one this post describes do show however that although the abstraction looks really nice, and simplifies asynchronous code quite a bit when it’s applicable, it is always leaky, and it’s important to be aware of what’s really happening under the appearances of magic.

No Comments