我的Firefox扩展程序提供了一个网站可用于访问插件功能的JavaScript功能。该网站调用此功能并提供两个回调。
网站代码:
function onButtonClick() {
var callbackSuccess = function() { alert("Yeah!"); };
var callbackError = function() { alert("Oh no!"); };
if (window.magicAddon) { // Check if my addon is installed
magicAddon.doStuff(callbackSuccess, callbackError);
}
}
内容脚本:
unsafeWindow.magicAddon = {
doStuff: function(callbackSuccess, callbackError) {
// Bind the two callbacks to events. The addon will fire one of them
self.port.on("doStuffSuccess", callbackSuccess);
self.port.on("doStuffError", callbackError);
// Fire the event that lets the addon do stuff
self.port.emit("doStuff");
}
};
在第一次调用时效果很好,但下次网站调用doStuff()时,新的侦听器会加起来并且alert()会执行两次。下次三个警报,等等。
任何想法如何优雅地避免听众加起来?我可以完全清除事件类型吗?
到目前为止没有效果:
self.port.once(..)
代替,因为我有两个回调事件:只有被回火的事件被清除,另一个停留并与下一个事件相加。self.port.removeListener
删除旧的侦听器,因为我没有旧的回调引用。 问题似乎与How to remove an event listener?类似,只是他使用了一个回调侦听器,因此可以使用self.port.once(..)
。
答案 0 :(得分:1)
您可以使用self.port.once
,然后手动删除其他回调:
doStuff: function(callbackSuccess, callbackError) {
// Bind the two callbacks to events. The addon will fire one of them
self.port.once("doStuffSuccess", function() {
callbackSuccess();
self.port.removeListener(callbackError);
});
self.port.once("doStuffError", function() {
callbackError();
self.port.removeListener(callbackSuccess);
});
// Fire the event that lets the addon do stuff
self.port.emit("doStuff");
}
您使用的是内容脚本,因此您无法完全清除事件类型,只能在主要附加代码中使用低级API。
但是,我建议避免unsafeWindow
提供此类功能,因为它是不安全的。如果您保持API异步,则可以使用内容脚本和页面之间的postMessage
管道来执行相同操作;并提供一个单独的javascript文件,人们可以在其网站中包含一个postMessages调用的抽象(例如magicAddon.doStuff()
)。如果需要,您还可以在网站中自动从您的插件中注入该脚本。
处理此机制肯定有点复杂,但您可以避免使用unsafeWindow
。
您可以找到有关内容脚本通信here的更多信息。
希望它有所帮助!
更新:要回复您的评论,您需要一个变量来跟踪doStuff
来电活动:
doStuff: function() {
var executing = false;
return function(callbackSuccess, callbackError) {
if (executing)
return;
executing = true;
// Bind the two callbacks to events. The addon will fire one of them
self.port.once("doStuffSuccess", function() {
executing = false;
callbackSuccess();
self.port.removeListener(callbackError);
});
self.port.once("doStuffError", function() {
executing = false;
callbackError();
self.port.removeListener(callbackSuccess);
});
// Fire the event that lets the addon do stuff
self.port.emit("doStuff");
}
}()
注意最后的()
。基本上这样,在doStuff
末尾设置的函数是我们最初分配的函数的结果。
通过这种方式,我们为doStuff
方法创建了一个闭包,其中executing
变量存在,并且如果已经执行doStuff
则保持跟踪,以便丢弃任何其他doStuff
致电,直到完成。
注意:即使在这种情况下javascript不是必需的,也可以是括号中的一个好的约定函数,以识别这个函数是'自我执行':`doStuff:(function(){。 ..}())
你也可以使用magicAddon
对象的属性来完成这项工作,但是在这种情况下它会被曝光。
答案 1 :(得分:1)
您必须考虑网站在第一次通话中收到回复之前可能再次拨打magicAddon.doStuff()
的可能性。因此,在任何时间点都可能有多个调用执行 - 您应该确保调用正确的侦听器。此外,如果任何一个回调触发,您需要删除两个侦听器 - 否则您将泄漏内存。以下是这可行的方法:
doStuff: function(callbackSuccess, callbackError) {
// Generate a random call ID
var callID = Math.random();
// Bind the two callbacks to events. Make sure to only act on events with the
// right call ID.
function onSuccess(id) {
if (id == callID) {
callbackSuccess();
removeListeners();
}
}
function onError(id) {
if (id == callID) {
callbackError();
removeListeners();
}
}
function removeListeners() {
self.port.removeListener("doStuffSuccess", onSuccess);
self.port.removeListener("doStuffError", onError);
};
self.port.on("doStuffSuccess", onSuccess);
self.port.on("doStuffError", onError);
// Fire the event that lets the addon do stuff
self.port.emit("doStuff", callID);
}
这使用随机呼叫ID来识别呼叫 - 代码处理doStuff
事件获取呼叫ID作为参数,并需要在doStuffSuccess
或doStuffError
事件中将其发回确保调用正确的回调。