Сравнение стеков контекста выполнения в 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 Хотя браузеры поддерживают несколько потоков (через веб-работников), они не поддерживают одну и ту же функцию, используемую в нескольких потоках; каждый поток получает свой собственный глобальный контекст.