Wednesday, November 28, 2012

Dealing With Nested Callback Sequences

I'm still new to this whole asynchronous/callback focused world, so in an effort to force myself to keep this blog going, I'm going to talk about one way I just worked out how to make it a bit less ugly.

My situation: I'm writing some tests for an object that updates its members when you call a function, and notifies the caller when it's done by using a callback. I want to test that a particular sequence of member updates happen after a certain number of function calls. This was my first attempt (I'm writing Node.js tests in TypeScript using Mocha, I've omitted the mocking I use to make the updates work but you should get the gist of it):
import assert = module("assert")

describe('Obj', function() {
    describe('#updateFunction()', function () {
        it('should work', function (done) {
            var obj = new Object();
            var expected1: { ... };
            var expected2: { ... };
            var expected3: { ... };

            // mock etc

            obj.updateFunction(() => {
                assert.deepEqual(expected1, obj.member);
                obj.updateNowPlaying(() => {
                    assert.deepEqual(expected2, obj.member);
                    obj.updateNowPlaying(() => {
                        assert.deepEqual(expected3, obj.member);
                        done();
                    });
                });
            });
        });
    });
});

Not great. Especially when I wanted to test a longer sequence of expectations.

Then I remembered a little thing called recursion and felt stupid for not thinking of it earlier:
import assert = module("assert")

describe('Obj', function() {
    describe('#updateFunction()', function () {

        var chainTestUpdateFunction = (obj: Object, expected: any[], done: () => void) => {
            if (expected.length == 0) {
                done();
            }
            else {
                obj.updateFunction(() => {
                    var curExpectation = expected.shift();
                    assert.deepEqual(curExpectation, obj.member);
                    chainTestUpdateFunction (station, expected, done);
                });
            }
        };

        it('should work', function (done) {
            var obj = new Object();
            var expected1: { ... };
            var expected2: { ... };
            var expected3: { ... };

            // mock etc
            var expected = [expected1, expected2, expected3];
            chainTestNowPlaying(obj, expected, done);
        });
    });
});

So, another pretty obvious solution that probably isn't even really worth blogging about. But, this is the internet after all.

Sunday, November 25, 2012

Timezones in Node

I won't get into the details of my current little project but it involves porting over some functionality from Python. This has generally been going well, but I ran into a problem when I was trying to port over this bit of code to get a formatted time in a particular timezone:
import pytz, datetime

timeformat = '%Y-%m-%dT%H:%M:%S.%f'
timezone = pytz.timezone('US/Pacific')
timenow = datetime.datetime.now(timezone)
formattedtime = timenow.strftime(timeformat)

I actually only realized that I needed this after deploying my app to Azure. My local time is in the right timezone so everything was working locally, but apparently Azure servers run in UTC and, after a bit of poking around, I discovered that there was no good way of changing this.

I figured this would be a fairly simple fix, since this is surely a common task, so I started searching around. I found two packages that seemed to do what I wanted, node-time and timezone-js. node-time looked ideal, but I was unable to pull it in to my dependencies without running into compile errors. I'm sure I could have worked it out eventually, but I wasn't too confident in being able to fix it on the Azure side so I gave up on that. TimezoneJS also looked promising, but seems like it was designed for client-side code rather than backend Node.js. A few hours later I've worked out how to set it up properly for node, so I figured I should write a bit about it.

I'm using TypeScript, so I made a definition file and slowly filled it in with the required definitions as I wrote the code. I will probably clean it up and submit it to this lovely repo eventually, but I've just put it up on a gist for now.

Since TimezoneJS is designed with client-side in mind, its default setup is to only pull in timezone information as it is required and to do that by using web requests. It took a bit of poking around the code, but it turns out that it is possible to set it up how you would expect server-side code to work, by getting it to pull in all timezone information from the filesystem on startup. The first step is to download the timezone files as it says on the setup instructions and put them in a folder in the project root. You then have to tell the library where that folder is, get it to load everything straight away, and change the transport method so that it loads from file rather than requesting a URL (this loader actually comes from the test-utils.js file in the timezone-js repo). You then initialize the timezones, I chose to do this synchronously since I'm doing it on startup so making this a blocking call isn't really an issue.
import tzjs = module("timezone-js")
import fs = module('fs');

export function init() {
    tzjs.timezone.zoneFileBasePath = './tz';
    tzjs.timezone.loadingScheme = tzjs.timezone.loadingSchemes.PRELOAD_ALL;
    tzjs.timezone.transport = function (opts) {
        if (opts.async) {
            if (typeof opts.success !== 'function') return;
            opts.error = opts.error || console.error;
            return fs.readFile(opts.url, 'utf8', function (err, data) {
                return err ? opts.error(err) : opts.success(data);
            });
        }
        return fs.readFileSync(opts.url, 'utf8');
    };

    tzjs.timezone.init({ async: false });
}

Now we can create dates with a specified timezone (new tzjs.Date(timezoneName)) and set a date's timezone (date.setTimezone(timezoneName)), but it is still a bit of a hassle to format the date. This is where Moment.js comes in. Moment doesn't work too well with timezones, but we can use it to translate UTC times to the timezone we want by subtracting the timezone offset from our date objects. To make things as simple as possible, I made a little helper function that would give me a moment object, offset by a specified timezone:
import moment = module("moment")
import tzjs = module("timezone-js")

export function momentInTimezone(timezoneName: string): moment.Moment {
    var timezone = new tzjs.Date(timezoneName);
    return moment.utc().subtract('minutes', timezone.getTimezoneOffset());
}

And there you have it, after running init, I can now format the local time in any timezone in one line:
import dateutils = module("./dateutils")

dateutils.init();

console.log("Time in Seattle: " + dateutils.momentInTimezone('America/Los_Angeles').format('MMMM Do YYYY, h:mm:ss a'));
console.log("Time in Brisbane: " + dateutils.momentInTimezone('Australia/Brisbane').format('MMMM Do YYYY, h:mm:ss a'));
console.log("Time in UTC: " + dateutils.momentInTimezone('UTC').format('MMMM Do YYYY, h:mm:ss a'));

Output:
Time in Seattle: November 25th 2012, 2:58:06 pm
Time in Brisbane: November 26th 2012, 8:58:06 am
Time in UTC: November 25th 2012, 10:58:06 pm

I've put the full dateutils file on a gist, but I'm tempted to turn it into my first public npm package.

Hello, World!

Yes, that is the most uncreative first post title I could have picked.

So I'm starting yet another coding blog and writing "so I'm starting yet another coding blog" yet again. Recently I've coding in areas that are actually useful, so this time I might be able to write about stuff that other people find useful.

My current project is a Node.js app written in TypeScript using Visual Studio, and I'm only a week in but it's already been a pretty interesting journey, so I'll be posting a bit about that. My last project was nice little bit of pure client-side JS code so I'll try to write a little about that too.

Welcome. To zombo.com.