Last active
October 12, 2017 12:21
-
-
Save a-ignatov-parc/59ede899f5d818a8984d538386b095fa to your computer and use it in GitHub Desktop.
Idempotent `Function.bind()`. Helpful when using with react.js to bind arguments to event handler
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const dictionary = new WeakMap(); | |
const fallbacks = new Map(); | |
const VALUE = '_value'; | |
function resolveLeaf(target, path) { | |
const tail = path.reduce((branch, item) => { | |
if (!branch.has(item)) branch.set(item, new Map()); | |
return branch.get(item); | |
}, target); | |
return { | |
has: () => VALUE in tail, | |
get: () => tail[VALUE], | |
set: value => { | |
tail[VALUE] = value; | |
}, | |
}; | |
} | |
export default function idempotentBind(handler, thisArg, ...args) { | |
if (typeof handler !== 'function') { | |
throw new TypeError('handler must be function'); | |
} | |
let ctx; | |
const type = typeof thisArg; | |
if (type !== 'object' || type !== 'function' || thisArg === null) { | |
if (!fallbacks.has(thisArg)) fallbacks.set(thisArg, {}); | |
ctx = fallbacks.get(thisArg); | |
} else { | |
ctx = thisArg; | |
} | |
dictionary.has(ctx) || dictionary.set(ctx, new Map()); | |
const tree = dictionary.get(ctx); | |
const path = [handler, ...args]; | |
const leaf = resolveLeaf(tree, path); | |
leaf.has() || leaf.set(handler.bind(thisArg, ...args)); | |
return leaf.get(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import bind from './bind'; | |
describe('Idempotent bind', () => { | |
it('must properly handle different arguments', () => { | |
expect(() => bind()).toThrow(TypeError); | |
expect(() => bind(() => {})).not.toThrow(TypeError); | |
expect(() => bind(() => {}, null)).not.toThrow(TypeError); | |
expect(() => bind(() => {}, true)).not.toThrow(TypeError); | |
expect(() => bind(() => {}, false)).not.toThrow(TypeError); | |
expect(() => bind(() => {}, 1)).not.toThrow(TypeError); | |
expect(() => bind(() => {}, 0)).not.toThrow(TypeError); | |
expect(() => bind(() => {}, '1')).not.toThrow(TypeError); | |
expect(() => bind(() => {}, '')).not.toThrow(TypeError); | |
}); | |
it('must properly bind context and args', () => { | |
function handler(...args) { | |
return { | |
args, | |
self: this, | |
}; | |
} | |
const obj = { | |
foo: 'bar', | |
}; | |
const bound1 = bind(handler); | |
const bound2 = bind(handler, null); | |
const bound3 = bind(handler, true); | |
const bound4 = bind(handler, 1); | |
const bound5 = bind(handler, '1'); | |
const bound6 = bind(handler, obj); | |
const bound7 = bind(handler, obj, 1); | |
const bound8 = bind(handler, obj, 1, true); | |
const bound9 = bind(handler, obj, 1, true, '1'); | |
expect(bound1()).toEqual({ args: [], self: undefined }); | |
expect(bound2()).toEqual({ args: [], self: null }); | |
expect(bound3()).toEqual({ args: [], self: true }); | |
expect(bound4()).toEqual({ args: [], self: 1 }); | |
expect(bound5()).toEqual({ args: [], self: '1' }); | |
expect(bound6()).toEqual({ args: [], self: obj }); | |
expect(bound7()).toEqual({ args: [1], self: obj }); | |
expect(bound8()).toEqual({ args: [1, true], self: obj }); | |
expect(bound9()).toEqual({ args: [1, true, '1'], self: obj }); | |
}); | |
it('must return same bound function', () => { | |
function handler() {} | |
const obj = { | |
foo: 'bar', | |
}; | |
[ | |
[handler], | |
[handler, 1], | |
[handler, true], | |
[handler, '1'], | |
[handler, null], | |
[handler, obj], | |
[handler, obj, 1], | |
[handler, obj, 1, true], | |
[handler, obj, 1, true, '1'], | |
].forEach(args => { | |
const bound1 = bind(...args); | |
const bound2 = bind(...args); | |
expect(bound1).toBe(bound2); | |
}); | |
}); | |
it('must return different bound function', () => { | |
function handler() {} | |
const bound1 = bind(handler, 1); | |
const bound2 = bind(handler, 2); | |
expect(bound1).not.toBe(bound2); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment