Skip to content

Instantly share code, notes, and snippets.

@a-ignatov-parc
Last active October 12, 2017 12:21
Show Gist options
  • Save a-ignatov-parc/59ede899f5d818a8984d538386b095fa to your computer and use it in GitHub Desktop.
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
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();
}
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