As always, try to read the entire post before leaving. I edited the end to add a cool update!

I first blogged about OpenWhisk sequences a few months ago, but if you didn't read that post, you can think of them as a general way of connecting multiple different actions together to form a new, grouped action. As an example, and this is something I'm actually working on, I may have an action that gets the latest tweets from an account as well as an action that performs tone analysis. I can then combine the two into one sequence that returns the tone for a Twitter account. (Technically I may need a third action in the middle to 'massage' the data between them.) This morning I was a bit curious about how errors are handled in sequences. I had my assumptions, but I wanted to test them out to be sure. Here is what I found.

I began by creating three simple actions, alpha, beta, and gamma. Here is the source for all three:

//Alpha
function main(args) {
	return {result:1};
}

//beta
function main(args) {
	if(!args.result) args.result=0; 
	return {result:args.result+1};
}

//gamma
function main(args) {
	if(!args.result) args.result=0; 
	return {result:args.result+1};
}

I called my sequence, testSequence1, because I'm a very creative individual. I then ran it to be sure I got the right result, 3. Cool.

Alright, so first, I broke beta:

function main(args) {
	if(!args.result) args.result=0; 
	doBad();
	return {result:args.result+1};
}

When run, I got an error I pretty much expected:

Error

Cool. So next I converted Beta to use promises:

function main(args) {

	return new Promise( (resolve, reject) => {
		if(!args.result) args.result=0; 
		//doBad();
		resolve({result:args.result+1});
	});
}

I didn't include an error this time, I just made sure it worked as expected, and it did. Cool. So first I added back in my doBad call:

function main(args) {

	return new Promise( (resolve, reject) => {
		if(!args.result) args.result=0; 
		doBad();
		//resolve({result:args.result+1});
	});
}

The result was a bit less helpful:

Error?

While not helpful, it is kind of expected. If you work with promises and don't look for an error, they tend to get swollowed up. (I believe Chrome recently adding support for noticing unhandled exceptions, but again, I think that was a recent change.)

I then switched to this version:

function main(args) {

	return new Promise( (resolve, reject) => {
		if(!args.result) args.result=0; 
		//doBad();
		//resolve({result:args.result+1});
		reject(new Error("Because the sky is blue."));
	});
}

And the result was the exact same. I had thought the more formal reject would have - possibly - be handled better, but it was not.

But - the good news - is that in every case where I tested my error, the metadata did correctly return a false result for the success key. While the verbosity/reason of the error was lost in the promise, the fact that an error was thrown was still something that could be recorded.

All in all - this really speaks to using good testing, and properly handling code that can throw exceptions. This falls into the "obvious" category, but as easy as OpenWhisk and serverless is in general, it is absolutely not a blank check to skip best practices when it comes to handling potential errors.

Edit: Once again, my good buddy Carlos Santana comes to the rescue with a good fix. If you reject a plain JavaScript object, and not an Error object, you get a better handled result. Here is an updated beta.js:

function main(args) {

	return new Promise( (resolve, reject) => {
		if(!args.result) args.result=0; 
		//doBad();
		//resolve({result:args.result+1});
		let e = new Error("Because the sky is blue.");
		reject({
			name:e.name,
			message: e.message,
			stack:e.stack
		});
	});
}

And here is the result:

Better error

Nice! The use of "error like" keys is arbitrary of course. I modified the reject to include howBlue:"very" and it was available in the result. It probably makes sense to follow a pattern like Carlos used above, just remember you can include additional stuff if you want as well.