Skip to content

Instantly share code, notes, and snippets.

@domachine
Last active February 2, 2016 09:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save domachine/0750b3dfc5eb694619ea to your computer and use it in GitHub Desktop.
Save domachine/0750b3dfc5eb694619ea to your computer and use it in GitHub Desktop.
Side-effect-free side effects
'use strict';
const assert = require('assert');
// Simple goal is to push out side-effects for testability
function liftMethod(ctx, method, opts) {
return lift(ctx[method], Object.assign({ ctx }, opts || {}));
}
function lift(fn, opts) {
const saneOpts = opts || { sync: false };
const ctx = saneOpts.ctx;
return function() {
return { fn, ctx, opts: saneOpts, args: [].slice.call(arguments) };
};
}
function run(it, done) {
return function() {
runIO(it.apply(this, arguments), {}, done);
};
}
// Takes an iterator and runs its instructions.
function runIO(it, param, done) {
let value;
try {
value = param.err
? it.throw(param.err)
: it.next(param.res);
} catch (e) {
return done(e);
}
if (value.done) return done(value.value);
runEffect(value.value, next);
function next(err, res) {
runIO(it, { err, res }, done);
}
}
function runEffect(effect, next) {
if (effect.opts.sync) {
// Run synchronous effect
try {
const ret = effect.fn.apply(effect.ctx, effect.args);
next(null, ret);
} catch (e) {
next(e);
}
} else {
// Run asynchronous effect
effect.fn.apply(effect.ctx, effect.args.concat(next));
}
}
// Example:
function *heavyIO(req, res) {
const user = yield lift(getUser)();
const send = liftMethod(res, 'send', { sync: true });
let articles;
try {
articles = yield lift(getUserArticles)(user._id);
} catch (e) {
yield send('Error fetching articles!');
return;
}
yield send(articles);
}
function getUser(done) {
// Implementation doesn't even matter for the test.
const user = { _id: 3 };
done(null, user);
}
function getUserArticles(u, done) {
// Implementation doesn't even matter for the test.
const articles = [{ title: 'foo' }];
done(null, articles);
}
const req = {};
const res = { send: () => { console.log('sending response'); } };
// Just fun to test. No database setup needed.
function testHeavyIO() {
const user = { _id: 3 };
const articles = [{ title: 'foo' }];
const it = heavyIO(req, res);
const send = liftMethod(res, 'send', { sync: true });
assert.deepEqual(it.next().value, lift(getUser)());
assert.deepEqual(it.next(user).value, lift(getUserArticles)(user._id));
assert.deepEqual(it.next(articles).value, send(articles));
console.log('test successful!');
}
function testHeavyIOFailure() {
const user = { _id: 3 };
const it = heavyIO(req, res);
const send = liftMethod(res, 'send', { sync: true });
assert.deepEqual(it.next().value, lift(getUser)());
assert.deepEqual(it.next(user).value, lift(getUserArticles)(user._id));
assert.deepEqual(
it.throw(new Error('foo')).value,
send('Error fetching articles!')
);
console.log('failure test successful!');
}
testHeavyIO();
testHeavyIOFailure();
// Run
run(heavyIO, end)(req, res);
function end(err) {
if (err) {
console.log('Something went wrong', err.stack);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment