Illegal invocation error using ES6 Proxy and node.js

£可爱£侵袭症+ 提交于 2019-12-09 03:23:19

问题


I can not figure out why the following code does not work:

var os = new Proxy(require('os'), {});
console.log( os.cpus() ); // TypeError: Illegal invocation

whereas

var os = require('os');
console.log(Reflect.apply(os.cpus, os, []));

or

var os = new Proxy(require('os'), {});
console.log( os.platform() );

works as expected.


回答1:


Having just skim read the source for the os package in the Node repo, it appears that the cpus() is exported from binding.getCPUs which is a C hook in the Node runtime environment.

cpus() therefore has the binding object as a function context, which is then lost through the proxy, giving you the IllegalInvocation error because there is no context to the function when you call it — although I'm hazy on the details.

platform() on the other hand is exported as function () { return process.platform; }, and hence it's just a function that returns an object, and doesn't need to be run under a specific context because Node function contexts will have the process variable specified by default (unless it has been overridden).

The following behaviour shows that applying the os as a context to the cpus function will work — proxies on function objects evidently lose the function context when calling properties.

const os = require('os');
const proxy = new Proxy(os, {});  // proxy of object, functions called get proxy context rather than os context
const cpus = new Proxy(os.cpus, {});  // proxy of function, still has os context

console.log(os.cpus());  // works (duh)
console.log(cpus());     // works
console.log(proxy.cpus.apply(os, []));  // works
console.log(proxy.cpus());  // fails with IllegalInvocation

Note: If someone can clear up the details on the JS function context for an answer I'd love to read it too.




回答2:


How about composition:

const os = require('os');
const proxy = new Proxy(os, {});
Object.getOwnPropertyNames(os).forEach(k => {
    var v = os[k];
    if(typeof v === "function") proxy[k] = v.bind(os);
});

//the `!!` because I don't want the actual print
//only a `true` or an `Error`
console.log(!!os.cpus());
console.log(!!proxy.cpus());
console.log(!!proxy.cpus.apply(proxy, []));

and all this as a utility function to "replace" new Proxy(), where handler.bindTargetFunctions can be

  • either an array of keyNames to be bound (so you can be specific)
  • or any truthy or falsy value to determine wether all functions on the target should be bound

the code:

function proxy(target, handler){
    const _proxy = new Proxy(target, handler);
    if(handler.bindTargetFunctions){
        let bindTargetFunctions = handler.bindTargetFunctions;
        if(!Array.isArray(bindTargetFunctions)){
            bindTargetFunctions = Object.getOwnPropertyNames(target)
                .filter(key => typeof target[key] === "function");
        }
        bindTargetFunctions.forEach(key => {
            _proxy[key] = target[key].bind(target);
        });
    }
    return _proxy;
}

const os = proxy(require('os'), { bindTargetFunctions: true });
//or
//const os = proxy(require('os'), { bindTargetFunctions: ["cpus"] });

console.log(os.cpus());

Edit:

Currently I try to bind functions directly in my get handler (see github.com/FranckFreiburger/module-invalidate/blob/master/…)‌​, the drawback of my solution is that each access to a function returns a new binding.

I entioned caching in the comments. This is how this cache could look like:

function createProxy(mod){
    var cache = Object.create(null);

    return new Proxy(function(){}, {
        get(target, property, receiver) {
            var val = Reflect.get(mod._exports, property, receiver);
            if(typeof val === "function"){
                if(!(property in cache) || cache[property].original !== val){
                    cache[property] = {
                        original: val,
                        bound: bal.bind(mod._exports)
                    }
                }
                val = cache[property].bound;
            }else if(property in cache){
                delete cache[property];
            }

            return val;
        }
    });
}

And No, I don't consider this cache a regular object. Not because it inherits from null, but because logically, to me this is a dictionary/map. And I don't know any reason why you would ever extend or proxy a particular dictionary.



来源:https://stackoverflow.com/questions/42496414/illegal-invocation-error-using-es6-proxy-and-node-js

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!