Сравнение стеков контекста выполнения в JavaScript

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

Например, 1: если два независимых обработчика событий A и B сработали и каждый из них вызвал C, то я хочу знать, что вызов C был сделан в разных стеках контекста выполнения.

A->C
B->C

Например, 2: если функция A называется C, которая называется B и снова называется C, то я хочу убедиться, что два вызова C были сделаны в одном и том же стеке контекста выполнения.

A->C->B->C

Мне нужно это поведение, потому что я являюсь транзакцией приложения реализации в Javascript, и я хотел бы иметь возможность поддерживать вложенные транзакции. Поскольку мы пишем строгий код, я ищу решения, которые не используют аргументы, вызываемые, вызывающие объекты.

Также имейте в виду, что A, B, C все они могут быть асинхронными, и C не будет сразу запускаться до завершения при вызове. И вот почему использование счетчиков, как описано ниже, не будет работать в асинхронных сценариях

C() {
   return Promise.resolve().then(() => {
        let result = someFunction();
        return result;
   })
}

B() {
    doSomethingSync();

    if(someCondition) {
        C();
    }
}

С асинхронно. Использование счетчиков означает, что два независимых обработчика событий вызывают C, оба увеличивают счетчик, прежде чем любой из них получит шанс уменьшить его.

1 ответ

Я собираюсь предположить, что вы запускаете это в однопоточном контексте (например, браузер 1 или NodeJS).

Если это так, то разница между этими двумя сценариями заключается в том, что C вызывается посредством косвенной рекурсии, поэтому простая переменная состояния может сообщить вам, произошло ли это.

Самая простая форма - это, конечно, счетчик:

var callstoC = 0;
function C() {
    ++callsToC;
    // ...code here that checks `callsToC`: If it's `1`, it's not recursive;
    // if it's `> 1`, it's recursive (indirectly or directly)
    --callsToC;
}

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

Очевидно, это ключ к тому, что все маршруты из функции правильно записывают, что вы покинули функцию.

Живой пример:

function hook(selector, event, handler) {
  var elements = document.querySelectorAll(selector);
  for (var n = 0; n < elements.length; ++n) {
    elements[n].addEventListener(
      event,
      handler,
      false
    );
  }
}
hook("#btnA, #btnB, #btnD", "click", function() {
  console.log(this.id + " calling C");
  C(this.id == "btnD");
});

var callsToC = 0;
function C(flag) {
  ++callsToC;
  console.log("C called with flag = " + flag + ", callsToC = " + callsToC);
  if (flag) {
    D();
  }
  console.log("C about to decrement and exit, callsToC = " + callsToC);
  --callsToC;
}
function D() {
  console.log("D calling C");
  C(false);
  console.log("D done");
}
.as-console-wrapper {
  max-height: 80% !important;
}
<input type="button" id="btnA" value="A">
<input type="button" id="btnB" value="B">
<input type="button" id="btnD" value="D">


В комментарии вы были обеспокоены тем, что callsToC был глобальным. Это не должно быть, и я бы не стал делать это глобальным или даже настолько заметным, как C; Я просто не хотел усложнять пример. Я бы сделал это по-настоящему приватным для C, вот так:

var C = (function() {
    var callsToC = 0;
    return function C() {
        // ...
    };
})();

1 Хотя браузеры поддерживают несколько потоков (через веб-работников), они не поддерживают одну и ту же функцию, используемую в нескольких потоках; каждый поток получает свой собственный глобальный контекст.

Другие вопросы по тегам