Gleb Bahmutov PRO
JavaScript ninja, image processing expert, software quality fanatic
KENSHO
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vulputate, orci non porta dignissim, ipsum neque hendrerit neque, in molestie metus dolor quis erat. Sed semper sit function orci sed sagittis. Nulla ut ultricies arcu, eu rutrum nisi. In hac habitasse platea dictumst. Ut vel add (tellus. Ut a, suscipit b) diam interdum tortor tincidunt, { eu consectetur leo efficitur. Proin return a + b; tortor nisi, dignissim eu porta non, auctor in nisi. Cras suscipit augue nec felis ornare, non } elementum dolor blandit. Vestibulum auctor commodo nunc, id interdum ex auctor eu. Vivamus gravida interdum viverra. Nunc porta sodales ipsum, a tempus leo placerat vel. Quisque ut massa id elit facilisis porttitor at efficitur velit.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vulputate, orci non porta dignissim, ipsum neque hendrerit neque, in molestie metus dolor quis erat. Sed semper sit function orci sed sagittis. Nulla ut ultricies arcu, eu rutrum nisi. In hac habitasse platea dictumst. Ut vel add (tellus. Ut a, suscipit b) diam interdum tortor tincidunt, { eu consectetur leo efficitur. Proin return a + b; tortor nisi, dignissim eu porta non, auctor in nisi. Cras suscipit augue nec felis ornare, non } elementum dolor blandit. Vestibulum auctor commodo nunc, id interdum ex auctor eu. Vivamus gravida interdum viverra. Nunc porta sodales ipsum, a tempus leo placerat vel. Quisque ut massa id elit facilisis porttitor at efficitur velit.
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
function add(a, b) {
return a + b;
}
Removed unnecessary variable add
Kensho Node
Kensho Node
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 JavaScript
function fn(a, b, c) { ... }
var newFn = fn.bind(null, valueA, valueB);
newFn(valueC);
Kensho JavaScript
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 JavaScript
function updateUserInfo(userId, newInfo){ ... }
// vs
function updateUserInfo(newInfo, userId){ ... }
Kensho JavaScript
// user-service.js
function updateUserInfo(userId, newInfo) { ... }
// user-controller.js
var updateUser;
function onLogin(id) {
updateUser = updateUserInfo.bind(null, id);
}
Kensho JavaScript
$('form').on('submit', function onSubmitted(form) {
updateUser(form);
});
function fn(a, b, c) { ... }
var newFn = fn.bind(null, valueA, valueB);
Kensho Lodash and Ramda
// 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 Lodash and Ramda
// 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 Lodash and Ramda
// 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 Lodash and Ramda
// 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>)
authGet('/repos', ctrl.getRepos);
authGet('/repos/:user/:name', ctrl.getRepo);
authGet('/repos/view/:user/:name', ctrl.viewFile);
Kensho Lodash and Ramda
// 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 Lodash and Ramda
function fn(options) { ... }
var obind = require('obind');
var withBar = obind(fn, { bar: 'bar' });
withBar({ baz: 'baz' });
/*
equivalent to
foo({
bar: 'bar',
baz: 'baz'
})
*/
Kensho Lodash and Ramda
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
pointfree style
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
pointfree style
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 API design
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 API design
var printAll = R.forEach(print);
printAll(multiplyAll(numbers));
Kensho API design
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 multiplyAll = R.map(R.multiply(constant));
var printAll = R.forEach(print);
printAll(multiplyAll(numbers));
Kensho FP
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 FP
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 FP
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 Promises
var getJavaScriptFiles = require('q')
.denodeify(require('glob'))
.bind(null, '*.js');
getJavaScriptFiles()
.then(console.log)
.catch(console.error)
.done();
Kensho Promises
No more error handling or callback boilerplate
var Q = require('q');
function asyncF() {
var defer = Q.defer();
process.nextTick(function () {
defer.resolve('f');
});
return defer.promise;
}
Q('f')
// or
Promise.resolve('f')
Kensho Promises
promise
.then(function (x) {
return new Promise(function (resolve) {
resolve(x + 1)
})
})
Kensho Promises
promise
.then(function (x) {
return x + 1
})
// or
promise.then(R.inc)
Kensho Promises
promise
...
.then(function (x) {
console.log(x)
return x
})
promise
...
.then(R.tap(console.log))
Q(...)
.then(R.inc)
.then(R.tap(console.log))
.then(verify)
.done()
var printThenVerify = R.pipeP(
R.inc,
R.tap(console.log),
verify
)
Q(...)
.then(printThenVerify)
.done()
Kensho Promises
fs.readdirAsync('.')
.map(processFile, { concurrency: 3 })
.then(fileResults);
Kensho Promises
Kensho RxJS
QUnit.test('a test', function(assert) {
var done = assert.async();
asyncOperation()
.then(function () {
// assert something
done();
});
});
Kensho testing
// Mocha
it('works', function (done) {
asyncOperation()
.then(function () {
// assert something
done();
});
});
Kensho testing
// Mocha
it('works', function () {
return asyncOperation()
.then(function () {
// assert something
});
});
Kensho testing
// 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 OSS
ngDescribe({
modules: 'A',
inject: 'foo',
tests: function (deps) {
it('finally a test', function () {
deps.step()
expect(deps.foo).toEqual('bar')
})
})
})
Kensho OSS
it('does something', function () {
...
expect(foo).toEqual('bar');
});
test "does something" failed
Error:
What has actually failed and why?
Kensho defensive code
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 defensive code
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 defensive code
it('does something', function () {
...
expect(foo).toEqual('bar',
'expected foo ' + JSON.stringify(foo) +
' to equal "bar"');
});
Kensho defensive code
// require('lazy-ass');
it('does something', function () {
la(foo === 'bar',
'expected foo', foo, 'to equal "bar"');
});
Kensho defensive code
var helpfulDescribe = require('lazy-ass-helpful');
helpfulDescribe(function tests() {
it('does something', function () {
la(foo === 'bar');
});
});
Kensho defensive code
var helpfulDescribe = require('lazy-ass-helpful');
helpfulDescribe(function tests() {
it('does something', function () {
la(foo === 'bar',
'condition [foo === "bar"], foo is', foo);
});
});
Kensho defensive code
lazy-ass-helpful rewrites code using falafel
// ES5: 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);
...
};
Kensho ES6
// ES5: need all arguments after "y"
function f(x, y) {
var a = Array.prototype.slice.call(arguments, 2);
...
};
// using ES6 Array.from
function f(x, y) {
// need all arguments after "y"
var a = Array.from(arguments).slice(2);
...
};
// using ES6 rest parameter
function f(x, y, ...a) {
// a is an array of arguments after "y"
...
};
Kensho ES6
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 ES6
var foo = '...';
var o = {};
o[foo] = 42;
// es6
var foo = '...';
var o = {
[foo]: 42
}
Kensho ES6
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 ES6
Kensho Node
KENSHO
By Gleb Bahmutov
How to remove unnecessary code from your code, including asynchronous code and unit tests. Presented at NationJS Node day 2016 in Washington DC.
JavaScript ninja, image processing expert, software quality fanatic