destruction v2.0.3
destruction.js
Harness the power of destructuring assignment to simplify error handling in coroutines.
var destruct = require('destruction')
.setCoroutine(require('bluebird').coroutine)
.destruct;
var example = destruct(function* () {
var [err, result] = yield asyncOperation();
if (err) {
return console.error(err);
}
doStuffWith(result);
});
This contrasts with the approach taken by most coroutine implementations: exceptions. It generally looks something like this.
var example = makeCoroutine(function* () {
try {
var result = yield asyncOperation();
doStuffWith(result);
} catch (err) {
console.error(err);
}
});
Inspired largely by the Go programming language, destruction.js provides a wrapper for other coroutine implementations and makes coroutines truly analagous to nested callbacks and simplifies code if different errors require different responses. For example, here is an implementation with exceptions.
var example = makeCoroutine(function* () {
var a, b, c;
try {
a = yield asyncOperationA();
} catch (err) {
return handleErrorA(err);
}
try {
b = yield asyncOperationB();
} catch (err) {
return handleErrorB(err);
}
try {
c = yield asyncOperationC();
} catch (err) {
return handleErrorC(err);
}
doStuffWith(a, b, c);
});
Here is how it is done with destruction.js.
var example = destruct(function* () {
var [err, a] = yield asyncOperationA();
if (err) return handleErrorA(err);
var [err, b] = yield asyncOperationB();
if (err) return handleErrorB(err);
var [err, c] = yield asyncOperationC();
if (err) return handleErrorC(err);
doStuffWith(a, b, c);
});
What exactly is destructuring?
For the uninitiated, destructuring is a new feature of ECMAScript 6, the latest version supported by node.
var [a, b] = [1, 2];
// a is 1
// b is 2
This is the syntax used in destruction.js. The destruct
function passes an array of form [err, null]
or [null, result]
to the generator function. Destructuring assignment can then be used to concisely capture both return values. It is supported by default in node 6 and above and can be enabled with the --harmony
flag in some earlier versions, including 4.4.5 LTS.
Destructuring can also be used on objects.
var obj = { valueA: 1, valueB: 2 };
var { valueA: a, valueB: b } = obj;
// a is 1
// b is 2
You can read more about destructuring assignment in ES6 here.
Differences between destruction.js and try...catch
In javascript, try
...catch
blocks catch errors like trying to call a function that does not exist.
try {
'some string'.nonexistentFunction();
} catch (err) {
console.log('this will be printed');
}
This means that if you wrap your coroutine in a try
...catch
block, a typo in a function name will be treated the same as something like trying to read from a file that does not exist, so it is more difficult to distinguish between the two. In destruction.js, err
is only given a value if there is an error in the promise passed via yield
, so it will not capture typos. However, in many cases, like if you are using a coroutine as a handler for a route in a web server, you do want to catch these kinds of errors to prevent the entire server from crashing. You can still do this by wrapping the coroutine in a try
...catch
block like with traditional coroutines. You can gracefully handle programmer mistakes in production and distinguish them from errors that are expected. An error handler can be defined that will catch any uncaught errors.
var destruct = require('destruction')
.setCoroutine(require('bluebird').coroutine)
.setErrorHandler(function(err) {
console.error('there was an error');
console.error(err);
})
.destruct;
Do not use let
ES6 also introduced the let keyword to replace var
. It is more strict than var
and eliminates some of the odd behavior of javascript variables. However, this does not work well with destruction.js when there are multiple calls in the same coroutine that both use err
as an identifier.
let [err, result1] = yield asyncOperation1();
let [err, result2] = yield asyncOperation2();
This code will throw a TypeError
because the first line declares err
, and the second one declares it once again. The let
keyword does not permit this. But what if the second let
is removed?
let [err, result1] = yield asyncOperation1();
[err, result2] = yield asyncOperation2();
The TypeError
is gone, but since neither var
nor let
was used, result2
is a global variable, which is not good. If var
is used instead of let
, err
can be used for both without declaring any global variables.
var [err, result1] = yield asyncOperation1();
var [err, result2] = yield asyncOperation2();
Although I like using let
whenever I can, it is recommended to use var
for these declarations in case err
is declared multiple times.