ES6 Proxies: отслеживание выполнения неопределенных функций - PullRequest
0 голосов
/ 02 мая 2018

Допустим, у меня есть следующий объект с двумя функциями в качестве свойств:

const foo = {
  f1: () => {...},
  f2: () => {...},
}

Я хотел бы выполнить определенное действие (например, выдать пользовательскую ошибку), когда кто-то пытается выполнить функцию, которой нет в объекте foo.

Я пытался использовать get прокси , но это выдает ошибку, даже когда я не пытаюсь выполнить f3, например, в следующем коде :

if (foo.f3) {...}

Так как я могу написать свой прокси таким образом, чтобы foo.f3 возвращал undefined, как обычно, но foo.f3() выдает ошибку?

Ответы [ 3 ]

0 голосов
/ 03 мая 2018

Это то, что вы просите? https://jsfiddle.net/MasterJames/bhesz1p7/23/

[очевидно, вам нужно F12 ваши инструменты разработчика, чтобы увидеть вывод консоли или изменить по желанию]

Единственная реальная разница - возвращать неопределенное после броска. Это как если бы функция выполнялась без каких-либо действий, поскольку ее не существует.

Я уверен, что есть другое решение, основанное на реальном случае использования, но мне нравится идея / вопрос. Сохраняет стабильность и т. Д.

let foo = new Proxy(
    {
    f1: function (val) {
      console.log('  F1 value:' + val);
      return 'called OKAY with', val;
    }
  },
  {
    get: function(obj, prop) {
      console.log("obj:", obj, " prop:", prop);
      if (prop in obj) {
        console.log("Found:", prop);
        return obj[prop];
      }
      else {
        console.log("Did NOT find:", prop);
        throw new Error(`Foo.${prop} is undefined not called returning undefined`);
        return undefined;
      }
    }
  });

console.log("\nFoo Tester started");

console.log(' Does F1 exists', foo.f1 !== undefined);
console.log(' called F1 result:', foo.f1('passed') );

try {
    console.log(' Does F2 exists', foo.f2 !== undefined);
    console.log(' called F2 result:', foo.f2('passed') );
}
catch (err) {
    console.log(' Error calling F2:', err );
}

console.log("Foo Tester finished");

Не уверен, что вы хотите попытаться поймать или нет, это также зависит от вас, так что в конце проверяется, является ли она реальной и функция одинаковая, в зависимости от того, как вы собираетесь обрабатывать ошибку.

if (foo.f2 && foo.f2.constructor === Function && foo.f2()) console.log("okay!");

Опять вы называете создание оболочки SafeCall, как это или что-то среднее? возможно вызов foo 'customThrow', если он существует или что-то еще, так много возможностей с JS.

0 голосов
/ 06 мая 2018

Хорошо, это заняло у меня некоторое время, но теперь у меня есть решение.

Я не совсем понял ваш вопрос, который я переформулировал как вопрос в вопросе для себя, чтобы лучше понять проблему, поскольку она сложна.

По сути, вы хотите знать, вызывается ли он или нет, поэтому функция, которая вам нужна в прокси get 'is' isCalling '.

Решение не является чистым в JS Fiddle, потому что оно там грязное, по крайней мере, для решения такого рода проблем.

По сути, решением является предложение: «Вы должны использовать ошибку, чтобы получить трассировку стека, затем проследить исходный код, который вызывает, и найти правильную скобку или нет.», Чтобы определить, как он вызывается и возвращать тогда что хочешь).

[Обратите внимание, это зависит от вашего кода и от того, как вы его называете, поэтому вы можете настроить его по мере необходимости.]

Поскольку вам нужно найти местоположение в исходном коде, которое вызывается из него, лучше, если нет встроенного тега сценария, как в этом примере JSFiddle. Я использую externalHTML для получения исходного кода, когда arguments.callee.caller.toString () лучше из реального файла JS. Вы также не будете искажать расположение из трассировки стека из-за странного поведения, поэтому при использовании обычного файла JS код будет правильно выровнен с использованием других решений. Если кто-то знает, как получить чистый источник, который каждый раз совпадает с трассировкой ошибок с блоками тегов сценария и т. Д. Также обратите внимание, что такие сообщения, как Error.lineNumber, еще не созданы.

[Пожалуйста, не беспокойтесь об истории версий, это было кошмаром, чтобы разобраться с этим. И снова вам лучше использовать другие пакеты npm для создания исходного кода из частей трассировки стека.]

Во всяком случае, пример, который я считаю, достигает того, чего вы хотите, но в принципе демонстрирует, что вам нужно сделать лучше в данной реальной (без скрипки) ситуации. Я почти уверен, что это не очень хорошее решение для производства, и я не проверял время (скорость работы), но если это действительно так важно для вашей задачи (и нет лучшего решения, в котором я сомневаюсь), тогда оно будет работа.

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

Использование широко распространено, когда вы начинаете больше думать об этом, как я это делал в прошлом году, когда впервые сделал что-то подобное. Примерами могут служить дополнительная проверка выполнения функции Security Check, решение для отладки загадочных ошибок в реальном времени, способ выполнения функции по-разному без передачи дополнительных аргументов, убегающие рекурсивные циклы (какова длина стека) и многие другие.

https://jsfiddle.net/MasterJames/bhesz1p7/90/

let foo = new Proxy(
    {
    f1: function (val) {
      console.log('  F1 value:' + val);
      return 'called OKAY with', val;
    }
  },
  {
    isCalling: function() {
      let stk = new Error();
      let sFrms = this.stkFrms(stk.stack);
      console.log("stkFrms:", sFrms);

      //BETTER From real pure JS Source
      //let srcCod = arguments.callee.caller.toString()

      let srcCod = document.getElementsByTagName('html')[0].outerHTML.split("\n");
      let cItm = sFrms[(sFrms.length - 1)];
      if(cItm !== undefined) {
        let cRow = (parseInt(cItm[1]) - 3);
        let cCol = (parseInt(cItm[2]) + 1);
        let cLine = srcCod[cRow];
        let cCod = cLine.substr(cCol, 1);
        if(cCod === '(') return true;
      }
      return false;
    },
    stkFrms: function (stk) {
      let frmRegex1 = /^.*at.*\(.*\:([0-9]*)\:([0-9]*)\)$/;
      let frmRegex2 = new RegExp(frmRegex1.source, 'gm');
      let res = [], prc, mtch, frms = stk.match(frmRegex2);
      for(mtch of frms) {
        prc = frmRegex1.exec(mtch);
        res.push(prc);
      }
      return res;
    },
    get: function(obj, prop) {
      if (prop in obj) {
        console.log("Found:", prop);
        return obj[prop];
      }
      else {
        if(this.isCalling() === false) {
          console.log("Did NOT find:", prop);
          return undefined;
        }
        else {
          console.log("Did NOT find return custom throw function:", prop);
          return function() {throw new Error(`Foo.${prop} is undefined`);}
        }
      }
    }
  });

console.log("foo.f1:", foo.f1);
console.log("foo.f1('passed'):", foo.f1('passed'));

console.log("foo.f2:", foo.f2);
try {
    console.log("foo.f2('passed2'):", foo.f2('passed2'));
}
catch(err) {
    console.log("foo.f2('passed2') FAILED:", err);
}
console.log("'f2' in foo:", 'f2' in foo);

Ладно, пробежка по буквам:

Вы хотите проверить, что foo.f2 не определен, поэтому он возвращает его, потому что он не вызывается.

Если вы вызываете его (f2) без простой проверки сначала и, при необходимости, с ошибкой, и вы не хотите пытаться перехватить запрос, чтобы сгенерировать свою пользовательскую ошибку, основанную на имени функции, вы хотите, чтобы она возвращала фактическую функцию, которая выдаст пользовательскую ошибку.

Вы также хотите использовать 'in', чтобы увидеть, что он не определен, что аналогично false (возможно, взломайте его, чтобы отправить false вместо undefined через что-то вроде isCallingFromIn.

Я что-то пропустил? Разве это не то, что вы все считали невозможным?

0 голосов
/ 02 мая 2018

Вот частичное решение, вдохновленное Unmiss .

const handler = {
  get: function(obj, prop) {
    if (prop in obj) {
      return obj[prop];
    } else {
      return () => {
        throw new Error(`Foo.${prop} is undefined`);
      }
    }
  }
};

Проблема с этим заключается в том, что, хотя она выполняет задачу только выдачи ошибки, когда вы действительно пытаетесь выполнить Foo.f3(), поскольку Foo.f3 теперь равно тому, что анонимная функция больше не возвращает undefined, Это означает, что (насколько я могу судить) if (Foo.f3) {...} всегда будет возвращать true.

Редактировать: как @paulpro указывает:

Вы абсолютно не можете этого сделать. foo.f3 либо не определено, либо некоторые вызывается с пользовательской логикой; это не может быть и то и другое.

Лучшее, что мы могли бы сделать, это перехватить операторы f3 in foo с использованием ловушки has, но это означало бы, что if (f3 in foo) и if (foo.f3) теперь будут иметь разные результаты, что выглядит как большой красный флаг.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...