Недопустимая ошибка вызова с использованием прокси-сервера ES6 и node.js

Не могу понять, почему не работает следующий код:

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

тогда как

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

or

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

работает как положено.


person Franck Freiburger    schedule 27.02.2017    source источник
comment
Как ни странно, new Proxy(require('os').cpus, {}) работает нормально... Я так же озадачен, как и вы.   -  person Liam Gray    schedule 28.02.2017
comment
Я использую последнюю версию node.js (7.6.0) в Windows.   -  person Franck Freiburger    schedule 28.02.2017
comment
Теперь работает на узле v10   -  person laggingreflex    schedule 11.06.2018


Ответы (2)


Только бегло прочитав исходный код пакета os в Репозиторий узла, похоже, что cpus() экспортируется из binding.getCPUs который является хуком C в среде выполнения Node.

Таким образом, cpus() имеет объект binding в качестве контекста функции, который затем теряется через прокси-сервер, что приводит к ошибке IllegalInvocation, поскольку при вызове функции нет контекста, хотя я не уверен в деталях.

platform(), с другой стороны, экспортируется как function () { return process.platform; }, и, следовательно, это просто функция, которая возвращает объект, и ее не нужно запускать в определенном контексте, поскольку контексты функций Node будут иметь переменную process, указанную по умолчанию (если только она не была переопределено).

Следующее поведение показывает, что применение os в качестве контекста к функции cpus будет работать — прокси на объектах функции, очевидно, теряют контекст функции при вызове свойств.

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

Примечание. Если кто-то может прояснить детали контекста функции JS для ответа, я бы тоже хотел его прочитать.

person Liam Gray    schedule 27.02.2017
comment
Наконец-то мы получили нечто более непонятное, чем квантовая механика! - person Franck Freiburger; 28.02.2017

Как насчет состава:

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, []));

и все это как вспомогательная функция для "замены" new Proxy(), где handler.bindTargetFunctions может быть

  • либо массив keyNames для привязки (чтобы вы могли быть конкретными)
  • или любое истинное или ложное значение, чтобы определить, должны ли быть связаны все функции на цели

код:

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());

Изменить:

В настоящее время я пытаюсь связать функции непосредственно в своем обработчике получения (см. github.com/FranckFreiburger/module-invalidate/blob/master/…)‌​, ​​недостатком моего решения является то, что каждый доступ к функции возвращает новую привязку.

Я упомянул кэширование в комментариях. Вот как может выглядеть этот кеш:

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;
        }
    });
}

И Нет, я не считаю этот кеш обычным объектом. Не потому, что он наследуется от нуля, а потому, что логически для меня это словарь/карта. И я не знаю ни одной причины, по которой вы могли бы расширить или проксировать конкретный словарь.

person Thomas    schedule 04.03.2017
comment
Интересно! В настоящее время я пытаюсь связать функции непосредственно в моем обработчике get (см. github.com/FranckFreiburger/module-invalidate/blob/master/), недостатком моего решения является то, что каждый доступ к функции возвращает новую привязку. - person Franck Freiburger; 04.03.2017
comment
Также обратите внимание, что в вашем решении динамически добавляемые функции на цели не будут учитываться. - person Franck Freiburger; 04.03.2017
comment
@FranckFreiburger, вы можете кэшировать связанные функции либо по имени в объекте, либо по ссылке на функцию src в фактическом Map. - person Thomas; 04.03.2017
comment
@FranckFreiburger И у меня редко есть какие-либо динамически добавляемые функции для моих объектов; Я создаю объект один раз и оставляю интерфейс таким. Все остальное делает недействительным скрытый класс этого объекта и портит работу оптимизатора в современных JS-движках. - person Thomas; 04.03.2017
comment
Хорошо, но когда вы пишете это: module.exports.foo = function() {} module.exports.bar = function() {} вы изменяете объект, и это законно - person Franck Freiburger; 04.03.2017
comment
@FranckFreiburger, да, это законно, (и код в моем ответе правильно обработает это), но вы не расширяете объект позже, например setTimeout(function(){ module.exports.asdf = someNewFunction; }, 10000); или var foo = require(whatever); foo.myUtility = anotherFunction;. Это считается плохой практикой, поскольку PrototypeJS показал, как плохо вы можете напортачить с расширением посторонних объектов. - person Thomas; 04.03.2017
comment
Я полностью понимаю вашу точку зрения, но мне приходится иметь дело с кодом js, который не принадлежит мне. Взгляните на мой проект: github.com/FranckFreiburger/module-invalidate - person Franck Freiburger; 04.03.2017
comment
@FranckFreiburger Я обновил ответ относительно упомянутого кэширования. И я углублюсь в ваше репо; но позже. - person Thomas; 04.03.2017