From 7df08f96b7df1bd63488856a01e6f636a8223a98 Mon Sep 17 00:00:00 2001 From: Michael Mayer Date: Sat, 17 Sep 2016 19:47:24 +0200 Subject: [PATCH 1/2] add ES2015 Proxy approach (requires Node.js 6) --- proxy.js | 71 ++++++++++++++++++++++++++++++++ test-proxy.js | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 proxy.js create mode 100644 test-proxy.js diff --git a/proxy.js b/proxy.js new file mode 100644 index 0000000..a8b11a5 --- /dev/null +++ b/proxy.js @@ -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); +}; diff --git a/test-proxy.js b/test-proxy.js new file mode 100644 index 0000000..4b79181 --- /dev/null +++ b/test-proxy.js @@ -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'); +}); From 27cac163dea8f5ba30de460f1b1f3996abf1d3be Mon Sep 17 00:00:00 2001 From: Sam Verschueren Date: Thu, 21 Mar 2019 08:20:39 +0100 Subject: [PATCH 2/2] Spread arguments --- proxy.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/proxy.js b/proxy.js index a8b11a5..4a5fee7 100644 --- a/proxy.js +++ b/proxy.js @@ -1,13 +1,7 @@ 'use strict'; -const processFn = (fn, opts) => function () { - const that = this; +const processFn = (fn, opts) => function (...args) { 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) => { @@ -20,7 +14,7 @@ const processFn = (fn, opts) => function () { } }); - fn.apply(that, args); + fn.apply(this, args); }); };