Friday, September 17, 2010

How to Write Multiple Strings to a Single fab.stream

‹prev | My Chain | next›

I have been struggling a bit with an edge case of fab.stream—a (fab) app from the upcoming version 0.5 of fab.js. To explore that difficulty, I break down the problem into a very small (fab) app:
with ( fab )
with ( html )

( fab )

// Listen on the FAB port and establish the faye server
( listen, 0xFAB )

( PRE )
( callback_test )
() // /pre

();
As I found out last night, HTML (fab) apps that wrap content (like <pre>) do so by writing to the response stream, then reading from the upstream app response (e.g. callback_test above), then writing the closing tag to the response stream. The end result being that callback_test has upstream data in the response when it gets invoked.

It makes perfect sense but ended up causing trouble in certain situations. Specifically, when the upstream app writes multiple times to the response stream, then the HTML app will send multiple close tags.

This does not occur for simple apps. For instance if I grab a string foo from a function and write it to the response stream, then write the hard-coded string "bar" to the response stream like so:
function non_stream_test(write) {
var foo = function_that_returns_foo();
return write(foo)
("bar");
}
...It just works. Accessing this resource via curl returns:
cstrom@whitefall:~$ curl -N http://localhost:4011/
<pre>foobar</pre>
The foo + bar test is wrapped in the <pre> tag as expected.

Of course, I could also just as easily written to the response stream once like so:
function non_stream_test(write) {
var foo = function_that_returns_foo();
return write(foo + "bar");
}
My problem is that I think the first way is just so cool.

Anyhow.

As I mentioned, the above cases just work. Problems arise when dealing will callbacks—for example, looking up a list of players in a database. Since I am using Javascript, the database lookup expects to be called with function that it can invoke once it has retrieved the list of player. This is Javascript, so it is quite common.

In that case, I cannot return a write() of data. I only have access to the data inside the callback. Calling return inside the callback will return from the callback, not the function that originally requested the data. Fortunately, in v0.5 of fabjs, there is fab.stream to handle this.

Consider a silly callback that invokes the supplied function with a single argument, "foo":
function callback_with_foo(fn) { fn("foo") }
I can then get that value, along with "bar" by doing something like:
function callback_test(write) {
return write(function(write) {
return fab.stream(function(stream) {
callback_with_foo(function (foo) {
stream(write(foo));
stream(write("bar"));
stream(write());
});

});
});
}
This is the situation in which I will get a second closing <pre> tag:
cstrom@whitefall:~$ curl -N http://localhost:4011/
<pre>foo</pre>bar</pre>
Each stream(write()) still has the closing tag in the upstream just waiting to be sent back to the browser. And merrily sent it is.

For the past 3 nights, I have been banging my head against the proverbial wall trying to figure out a way around this. Today, it finally occurred to me that there is no need. I cannot think of a use case in which I would want to write twice in a callback and, even if I could, then it might warrant a bit more firepower than (fab) HTML apps. A perfectly cromulent solution is to simply accumulate the string data and then write it once:
function callback_test(write) {
return write(function(write) {
return fab.stream(function(stream) {
callback_with_foo(function (foo) {
stream(write(foo + "bar"));
stream(write());
});
});
});
}
(the second stream() with an empty write() signals to stream that it is done sending data)

With that, I get my properly wrapped text from a callback:
cstrom@whitefall:~$ curl -N http://localhost:4011/
<pre>foobar</pre>
Sometimes I can really obsess over very tiny, immaterial details. Such is my burden.

Tomorrow, I am definitely looking at something beside fab.stream!


Day #229

No comments:

Post a Comment