Skip to content

Commit

Permalink
add ES2015 Proxy approach (requires Node.js 6)
Browse files Browse the repository at this point in the history
  • Loading branch information
schnittstabil committed Jan 23, 2017
1 parent 8dc81c1 commit 6c25373
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 6 deletions.
11 changes: 8 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
language: node_js
node_js:
- '6'
- '4'
matrix:
include:
- node_js: '6'
env: NPM_TEST_SCRIPT=test
- node_js: '4'
env: NPM_TEST_SCRIPT=test-node4
script:
- npm run $NPM_TEST_SCRIPT
4 changes: 3 additions & 1 deletion optimization-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
'use strict';
const assert = require('assert');
const v8 = require('v8-natives');
const pify = require('./');

const args = process.argv.slice(2);
const pify = require(args[0]); // eslint-disable-line import/no-dynamic-require

function assertOptimized(fn, name) {
const status = v8.getOptimizationStatus(fn);
Expand Down
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@
},
"scripts": {
"test": "xo && ava && npm run optimization-test",
"optimization-test": "node --allow-natives-syntax optimization-test.js"
"test-node4": "xo && ava test.js && npm run optimization-test-index",
"optimization-test-index": "node --allow-natives-syntax optimization-test.js ./index",
"optimization-test-proxy": "node --allow-natives-syntax optimization-test.js ./proxy",
"optimization-test": "npm run optimization-test-index && npm run optimization-test-proxy"
},
"files": [
"index.js"
"index.js",
"proxy.js"
],
"keywords": [
"promise",
Expand Down
71 changes: 71 additions & 0 deletions proxy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
'use strict';

const processFn = (fn, opts) => function () {
const that = this;
const P = opts.promiseModule;
const args = new Array(arguments.length);

for (let i = 0; i < arguments.length; i++) {
args[i] = arguments[i];
}

return new P((resolve, reject) => {
args.push((err, result, ...results) => {
if (err) {
reject(err);
} else if (opts.multiArgs) {
resolve([result, ...results]);
} else {
resolve(result);
}
});

fn.apply(that, args);
});
};

module.exports = (obj, opts) => {
opts = Object.assign({
exclude: [/.+Sync$/],
promiseModule: Promise
}, opts);

const filter = key => {
const match = pattern => typeof pattern === 'string' ? key === pattern : pattern.test(key);
return opts.include ? opts.include.some(match) : !opts.exclude.some(match);
};

const cache = new Map();

const handler = {
get: (target, key) => {
let cached = cache.get(key);

if (!cached) {
const x = target[key];

cached = typeof x === 'function' && filter(key) ? processFn(x, opts) : x;
cache.set(key, cached);
}

return cached;
}
};

if (!opts.excludeMain) {
const main = Symbol('main');

handler.apply = (target, thisArg, argumentsList) => {
let cached = cache.get(main);

if (!cached) {
cached = processFn(target, opts);
cache.set(main, cached);
}

return cached.apply(thisArg, argumentsList);
};
}

return new Proxy(obj, handler);
};
111 changes: 111 additions & 0 deletions test-proxy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import fs from 'fs';
import test from 'ava';
import pinkiePromise from 'pinkie-promise';
import fn from './proxy';

const fixture = cb => setImmediate(() => cb(null, 'unicorn'));
const fixture2 = (x, cb) => setImmediate(() => cb(null, x));
const fixture3 = cb => setImmediate(() => cb(null, 'unicorn', 'rainbow'));
const fixture4 = cb => setImmediate(() => {
cb(null, 'unicorn');
return 'rainbow';
});

fixture4.meow = cb => {
setImmediate(() => {
cb(null, 'unicorn');
});
};

const fixture5 = () => 'rainbow';

const fixtureModule = {
method1: fixture,
method2: fixture,
method3: fixture5
};

test('main', async t => {
t.is(typeof fn(fixture)().then, 'function');
t.is(await fn(fixture)(), 'unicorn');
});

test('pass argument', async t => {
t.is(await fn(fixture2)('rainbow'), 'rainbow');
});

test('custom Promise module', async t => {
t.is(await fn(fixture, {promiseModule: pinkiePromise})(), 'unicorn');
});

test('multiArgs option', async t => {
t.deepEqual(await fn(fixture3, {multiArgs: true})(), ['unicorn', 'rainbow']);
});

test('wrap core method', async t => {
t.is(JSON.parse(await fn(fs.readFile)('package.json')).name, 'pify');
});

test('module support', async t => {
t.is(JSON.parse(await fn(fs).readFile('package.json')).name, 'pify');
});

test('module support - doesn\'t transform *Sync methods by default', t => {
t.is(JSON.parse(fn(fs).readFileSync('package.json')).name, 'pify');
});

test('module support - preserves non-function members', t => {
const module = {
method: () => {},
nonMethod: 3
};

t.deepEqual(Object.keys(module), Object.keys(fn(module)));
});

test('module support - transforms only members in options.include', t => {
const pModule = fn(fixtureModule, {
include: ['method1', 'method2']
});

t.is(typeof pModule.method1().then, 'function');
t.is(typeof pModule.method2().then, 'function');
t.not(typeof pModule.method3().then, 'function');
});

test('module support - doesn\'t transform members in options.exclude', t => {
const pModule = fn(fixtureModule, {
exclude: ['method3']
});

t.is(typeof pModule.method1().then, 'function');
t.is(typeof pModule.method2().then, 'function');
t.not(typeof pModule.method3().then, 'function');
});

test('module support - options.include over options.exclude', t => {
const pModule = fn(fixtureModule, {
include: ['method1', 'method2'],
exclude: ['method2', 'method3']
});

t.is(typeof pModule.method1().then, 'function');
t.is(typeof pModule.method2().then, 'function');
t.not(typeof pModule.method3().then, 'function');
});

test('module support — function modules', t => {
const pModule = fn(fixture4);

t.is(typeof pModule().then, 'function');
t.is(typeof pModule.meow().then, 'function');
});

test('module support — function modules exclusion', t => {
const pModule = fn(fixture4, {
excludeMain: true
});

t.is(typeof pModule.meow().then, 'function');
t.not(typeof pModule(() => {}).then, 'function');
});

0 comments on commit 6c25373

Please sign in to comment.