Gleb Bahmutov PRO
JavaScript ninja, image processing expert, software quality fanatic
KENSHO
var add = function (a, b) {
return a + b;
};
4 entities: a, b, add and a function
Each entity can interact with other 3
Kensho Node
var add = function (a, b) {
return a + b;
};
for N entities: N * (N - 1) interactions
Kensho Node
Kensho Node
Replace the boilerplate with equivalent short and readable code
Kensho Node
var path = require('path');
var first = path.join(__dirname, '../foo');
...
var second = path.join(__dirname, '../bar');
Kensho Node
var join = require('path').join;
var first = join(__dirname, '../foo');
...
var second = join(__dirname, '../bar');
Kensho Node
var relativePath = require('path')
.join.bind(null, __dirname);
var first = relativePath('../foo');
...
var second = relativePath('../bar');
Kensho Node
var first = path.join(__dirname, '../foo');
// vs
var second = relativePath('../bar');
Kensho Node
var relativePath = path.join.bind(null, __dirname);
relativePath('foo.js');
Kensho Node
function fn(a, b, c) { ... }
var newFn = fn.bind(null, valueA, valueB);
newFn(valueC);
Kensho Node
Place on the left arguments that are likely to be known first
function fn(a, b, c) { ... }
var newFn = fn.bind(null, valueA, valueB);
newFn(valueC);
Kensho Node
function updateUserInfo(userId, newInfo){ ... }
// vs
function updateUserInfo(newInfo, userId){ ... }
Kensho Node
// user-service.js
function updateUserInfo(userId, newInfo) { ... }
// user-controller.js
function onLogin(id) {
var updateUser = updateUserInfo.bind(null, id);
$('form').on('submit', function onSubmitted(form) {
updateUser(form);
});
}
Kensho Node
// user-service.js
function updateUserInfo(userId, newInfo) { ... }
// user-controller.js
function onLogin(id) {
var updateUser = updateUserInfo.bind(null, id);
$('form').on('submit', function onSubmitted(form) {
updateUser(form);
});
}
Kensho Node
// user-service.js
function updateUserInfo(userId, newInfo) { ... }
// user-controller.js
function onLogin(id) {
var updateUser = updateUserInfo.bind(null, id);
$('form').on('submit', updateUser);
}
Kensho Node
Kensho Node
function fn(a, b, c) { ... }
var newFn = fn.bind(null, valueA, valueB);
Kensho Node
// or
var _ = require('lodash');
var newFn = _.partial(fn, valueA, valueB);
// or
var R = require('ramda');
var newFn = R.partial(fn, valueA, valueB);
['1', '2', '3'].map(parseInt); // [1, NaN, NaN]
// function parseInt(x, radix)
// but Array.map sends (value, index, array)
['1', '2', '3'].map(function (x) {
return parseInt(x, 10);
});
['1', '2', '3'].map(_.partialRight(parseInt, 10));
// [1, 2, 3]
// radix is bound,
// index and array arguments are ignored
Kensho Node
// function parseInt(x, radix)
['1', '2', '3'].map(_.partial(parseInt, _, 10));
// [1, 2, 3]
// or
var S = require('spots');
['1', '2', '3'].map(S(parseInt, S, 10));
// [1, 2, 3]
Kensho Node
// Express
// same callbacks to check if the user is logged in and authorized
app.get('/repos',
passport.isAuthenticated, passport.isAuthorized, ctrl.getRepos);
app.get('/repos/:user/:name',
passport.isAuthenticated, passport.isAuthorized, ctrl.getRepo);
app.get('/repos/view/:user/:name',
passport.isAuthenticated, passport.isAuthorized, ctrl.viewFile);
app.get(<url>, passport.isAuthenticated, passport.isAuthorized, ...);
Kensho Node
// prefill 2 middle arguments using spots
var S = require('spots');
var authGet = S(app.get, S,
passport.isAuthenticated, passport.isAuthorized)
.bind(app);
// authGet(<url>, <controller>)
Kensho Node
authGet('/repos', ctrl.getRepos);
authGet('/repos/:user/:name', ctrl.getRepo);
authGet('/repos/view/:user/:name', ctrl.viewFile);
// divide by 10
function divide(a, b) { return a / b; }
var selective = require('heroin');
var by10 = selective(divide, { b: 10 });
console.log(by10(10)); // 1 (a = 10, b = 10)
console.log(by10(2)); // 0.2 (a = 2, b = 10)
Kensho Node
function fn(options) { ... }
var obind = require('obind');
var withBar = obind(fn, { bar: 'bar' });
withBar({ baz: 'baz' });
/*
equivalent to
foo({
bar: 'bar',
baz: 'baz'
})
*/
Kensho Node
var numbers = [3, 1, 7];
var constant = 2;
var k = 0;
for(k = 0; k < numbers.length; k += 1) {
console.log(numbers[k] * constant);
}
// 6 2 14
Kensho Node
function mul(a, b) {
return a * b;
}
function print(n) {
console.log(n);
}
numbers.map(function (n) {
return mul(n, constant);
}).forEach(print);
// 6 2 14
Kensho Node
function mul(a, b) {
return a * b;
}
var byK = mul.bind(null, constant);
var print = console.log.bind(console);
numbers
.map(byK)
.forEach(print);
Kensho Node
var numbers = [3, 1, 7];
var constant = 2;
function mul(a, b) {
return a * b;
}
var _ = require('lodash');
var byK = _.partial(mul, constant);
var print = _.bind(console.log, console);
_.forEach(_.map(numbers, byK), print);
Kensho Node
var mul = _.curry(function (a, b) {
return a * b;
});
var byK = mul(constant);
function mul(a, b) {
return a * b;
}
var byK = _.partial(mul, constant);
Curried functions have partial application built in.
Kensho Node
function cb(item, index, array) { ... }
// ES5 method
Array.prototype.map(cb);
// Lodash method
_.map(array, cb);
Place on the left arguments that are likely to be known first
Kensho Node
var R = require('ramda');
// callback is first argument
R.map(cb, array);
// all functions are curried
var by5 = R.multiply(5);
by5(10); // 50
Kensho Node
var numbers = [3, 1, 7];
var constant = 2;
var R = require('ramda');
var print = R.bind(console.log, console);
var multiplyAll = R.map(R.multiply(constant));
var printAll = R.forEach(print);
printAll(multiplyAll(numbers));
Kensho Node
var multiplyAll = R.map(R.multiply(constant));
var printAll = R.forEach(print);
printAll(multiplyAll(numbers));
Kensho Node
var multiplyAll = R.map(R.multiply(constant));
var printAll = R.forEach(print);
var computation = R.compose(printAll, multiplyAll);
computation (numbers);
Static logic
Dynamic data
Kensho Node
var mulPrint = R.pipe(
R.map(R.multiply(constant)),
R.tap(debugLog),
R.forEach(R.bind(console.log, console))
);
mulPrint (numbers);
R.pipe for readability
R.tap for debugging
Kensho Node
var glob = require('glob');
function getJavaScriptFiles(cb) {
glob('*.js', function (err, files) {
if (err) {
return cb(err);
}
cb(null, files);
});
}
getJavaScriptFiles(function (err, files) {
if (err) {
return console.error(err);
}
console.log(files);
});
Kensho Node
var getJavaScriptFiles = require('q')
.denodeify(require('glob'))
.bind(null, '*.js');
getJavaScriptFiles()
.then(console.log)
.catch(console.error)
.done();
Kensho Node
var Q = require('q');
function asyncF() {
var defer = Q.defer();
process.nextTick(function () {
defer.resolve('f');
});
return defer.promise;
}
function asyncAddOo(value) {
var defer = Q.defer();
process.nextTick(function () {
defer.resolve(value + 'oo');
});
return defer.promise;
}
function print(x) {
console.log('value =', x);
return x;
}
function verify(x) {
console.assert(x === 'foo');
}
asyncF()
.then(asyncAddOo)
.then(print)
.then(verify)
.done();
// "foo"
var Q = require('q');
function asyncAddOo(value) {
var defer = Q.defer();
process.nextTick(function () {
defer.resolve(value + 'oo');
});
return defer.promise;
}
function print(x) {
console.log('value =', x);
return x;
}
function verify(x) {
console.assert(x === 'foo');
}
Q('f')
.then(asyncAddOo)
.then(print)
.then(verify)
.done();
Kensho Node
var Q = require('q');
function addOo(value) {
return value + 'oo';
}
function print(x) {
console.log('value =', x);
return x;
}
function verify(x) {
console.assert(x === 'foo');
}
Q('f')
.then(addOo)
.then(print)
.then(verify)
.done();
Kensho Node
var Q = require('q');
var R = require('ramda');
function print(x) {
console.log('value =', x);
return x;
}
function verify(x) {
console.assert(x === 'foo');
}
Q('f')
// apply second arg
.then(R.add(R.__, 'oo'))
.then(print)
.then(verify)
.done();
Kensho Node
var Q = require('q');
var R = require('ramda');
function print(x) {
console.log('value =', x);
}
function verify(x) {
console.assert(x === 'foo');
}
Q('f')
// apply second arg
.then(R.add(R.__, 'oo'))
.then(R.tap(print))
.then(verify)
.done();
Kensho Node
var Q = require('q');
var R = require('ramda');
function print(x) {
console.log('value =', x);
}
function verify(x) {
console.assert(x === 'foo');
}
var printThenVerify = R.pipeP(
R.add(R.__, 'oo'),
R.tap(print),
verify
);
Q('f')
.then(printThenVerify);
Remove boilerplate while the code is still readable to you
Kensho Node
fs.readdirAsync('.')
.map(processFile, { concurrency: 3 })
.then(fileResults);
Kensho Node
QUnit.test('a test', function(assert) {
var done = assert.async();
asyncOperation()
.then(function () {
// assert something
done();
});
});
Kensho Node
// Mocha
it('works', function (done) {
asyncOperation()
.then(function () {
// assert something
done();
});
});
Kensho Node
// Mocha
it('works', function () {
return asyncOperation()
.then(function () {
// assert something
});
});
Kensho Node
// typical AngularJS unit test
describe('typical test', function () {
var $rootScope, foo;
beforeEach(function () {
angular.mock.module('A');
// other modules
});
beforeEach(inject(function (_$rootScope_, _foo_) {
$rootScope = _$rootScope_;
foo = _foo_;
}));
it('finally a test', function () {
$rootScope.$apply(); // for example
expect(foo).toEqual('bar');
});
});
Kensho Node
ngDescribe({
modules: 'A',
inject: ['$rootScope', 'foo'],
tests: function (deps) {
it('finally a test', function () {
deps.$rootScope.$apply();
expect(deps.foo).toEqual('bar');
});
});
});
Kensho Node
it('does something', function () {
...
expect(foo).toEqual('bar');
});
test "does something" failed
Error:
What has actually failed and why?
Kensho Node
it('does something', function () {
...
expect(foo).toEqual('bar',
'expected foo to equal "bar"');
});
test "does something" failed
Error: expected foo to equal "bar"
Why did it fail?
Kensho Node
it('does something', function () {
...
expect(foo).toEqual('bar',
'expected foo ' + foo + ' to equal "bar"');
});
test "does something" failed
Error: expected foo something to equal "bar"
Message repeats the predicate!
Kensho Node
it('does something', function () {
...
expect(foo).toEqual('bar',
'expected foo ' + JSON.stringify(foo) +
' to equal "bar"');
});
Kensho Node
// require('lazy-ass');
it('does something', function () {
la(foo === 'bar',
'expected foo', foo, 'to equal "bar"');
});
Kensho Node
var helpfulDescribe = require('lazy-ass-helpful');
helpfulDescribe(function tests() {
it('does something', function () {
la(foo === 'bar');
});
});
Kensho Node
var helpfulDescribe = require('lazy-ass-helpful');
helpfulDescribe(function tests() {
it('does something', function () {
la(foo === 'bar',
'condition [foo === "bar"], foo is', foo);
});
});
Executed code
Kensho Node
// need all arguments after "y"
function f(x, y) {
var a = Array.prototype.slice.call(arguments, 2);
...
};
// using lodash
function f(x, y) {
// need all arguments after "y"
var a = _.toArray(arguments).slice(2);
...
};
// using ES6 rest parameter
function f(x, y, ...a) {
// a is an array of arguments after "y"
...
};
Kensho Node
function f (x, y, z) {
if (y === undefined)
y = 7;
if (z === undefined)
z = 42;
return x + y + z;
};
// es6
function f (x, y = 7, z = 42) {
return x + y + z
}
Kensho Node
var foo = '...';
var o = {};
o[foo] = 42;
// es6
var foo = '...';
var o = {
[foo]: 42
}
Kensho Node
numbers.map(function (v) { return v + 1; });
// es6
numbers.map(v => v + 1);
// I prefer
var R = require('ramda');
var add1 = R.add(1);
numbers.map(add1);
Kensho Node
Kensho Node
By Gleb Bahmutov
How to remove unnecessary code from your code, including asynchronous code and unit tests. Presented at NodeJS Italy in 2015
JavaScript ninja, image processing expert, software quality fanatic