Could this cause a JavaScript memory leak? - javascript

I use a couple of functions heavily in my code. Now, as I'm looking for the source of high memory usage, I want to ensure that they're not the culprits.
Using jQuery, I bind and trigger custom events on the body element. When an event is triggered, I store it in a list of triggered events. One of the helper functions I use is called "waitfor". Here's some pseudocode:
waitfor = function(event, callback){
if(event_has_ever_been_called){
callback(); //RUN IMMEDIATELY
}
else{
//BIND CALLBACK TO RUN AS SOON AS THE EVENT IS TRIGGERED
$("body").bind(event, function(){
callback();
});
}
}
For example,
//ADD GOOGLE MAP TO PAGE
... listen for the google map 'idle' event,
... then call $("body").trigger("gmap.ready")
//ADD MARKERS AS SOON AS POSSIBLE (BUT NOT BEFORE)
waitfor("gmap.ready", function(){
//add markers
});
This seems very straightforward to me but I'm a little concerned that it (or any of my other functions that use anonymous callback functions) could be causing high memory usage.
Is this sufficient information to determine that this function is safe / not safe?

You should call one instead of bind to remove the handler after the event fires.
Otherwise, your function, and everything referenced by its closure, will stay referenced forever by jQuery's handler list.

Related

Create and dispatch events javascript. Code execution simple delegation

Comming from a c# background, I just want to create an event in a certain point of my code, soas to be dispatched elsewere, meaning that if in some part of the code there has been a subscription, this delegate function is called.
So I tried to do:
function myFunction() {
console.log("delegated call achieved!");
}
const myEvent = new Event('onMyConditionIsMet', myFunction, false);
//at this point the program the subscription takes place
function whatever1() {
//...not meaningfull code
myEvent.addEventListener('onMyConditionIsMet');
//myEvent += myFunction; c# way subscription in case it makes sense
}
//at this point in the program, event subscription is checked and
//delegate func run in case there has been a subscription
function whatever2() {
//...not meaningfull code
myEvent?.invoke(); // ?.invoke(); would be the c# way to do it.
}
All the examples I found are related to DOM events, but my case would be for events I create myself, think these are called synthetic events.
Another assumption I make in this question is that there would be no arguments in the delegate call function, so, just to be clear with the naming, it would be a delegate with no arguments. Just pointing this because in c# events are just delegate funcs with no arguments, so a specific type of delegate. Not sure if this works the same way in Javscript.
What would be the approach to do this? (Meaning creating a simple event instance, subscribing, and executing the delegated code if there is any subscription)?
I think the functionality you are looking for can be best obtained by using OOP/Classes.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#prototype_methods
Edit: see this also - "event" is deprecated, what should be used instead?

Check for async function re-entry in JS

Scenario:
We have a MutationObserver handler function handler.
In handler, we do some DOM manipulation that would trigger handler again. Conceptually, we would have a reentrant handler call. Except MutationObserver doesn't run in-thread, it will fire after the handler has already finished execution.
So, handler will trigger itself, but through the async queue, not in-thread. The JS debugger seems to know this, it will have itself as an async ancestor in the call stack (i.e. using Chrome).
In order to implement some efficient debouncing of events, we need to detect same; that is, if handler was called as a result of changes triggered by itself.
So how to do?
mutationObserver=new MutationObserver(handler);
mutationObserver.observe(window.document,{
attributes:true,
characterData:true,
childList:true,
subtree:true
});
var isHandling;
function handler(){
console.log('handler');
// The test below won't work, as the re-entrant call
// is placed out-of-sync, after isHandling has been reset
if(isHandling){
console.log('Re-entry!');
// Throttle/debounce and completely different handling logic
return;
}
isHandling=true;
// Trigger a MutationObserver change
setTimeout(function(){
// The below condition should not be here, I added it just to not clog the
// console by avoiding first-level recursion: if we always set class=bar,
// handler will trigger itself right here indefinitely. But this can be
// avoided by disabling the MutationObserver while handling.
if(document.getElementById('foo').getAttribute('class')!='bar'){
document.getElementById('foo').setAttribute('class','bar');
}
},0);
isHandling=false;
}
// NOTE: THE CODE BELOW IS IN THE OBSERVED CONTENT, I CANNOT CHANGE THE CODE BELOW DIRECTLY, THAT'S WHY I USE THE OBSERVER IN THE FIRST PLACE
// Trigger a MutationObserver change
setTimeout(function(){
document.getElementById('asd').setAttribute('class','something');
},0);
document.getElementById('foo').addEventListener('webkitTransitionEnd',animend);
document.getElementById('foo').addEventListener('mozTransitionEnd',animend);
function animend(){
console.log('animend');
this.setAttribute('class','bar-final');
}
#foo {
width:0px;
background:red;
transition: all 1s;
height:20px;
}
#foo.bar {
width:100px;
transition: width 1s;
}
#foo.bar-final {
width:200px;
background:green;
transition:none;
}
<div id="foo" ontransitionend="animend"></div>
<div id="asd"></div>
Note
Our use case comprises of 2 components here; one we will call contents which is any run-of-the-mill web app, with a lot of UI components and interface. And an overlay, which is the component observing the content for changes and possibly doing changes of its own.
A simple idea that is not enough is to just disable the MutationObserver while handling; or, assume every second call to handler as recursive; This does not work in the case illustrated above with the animationend event: the contents can have handlers which in turn can trigger async operations. The two most popular such issues are: onanimationend/oneventend, onscroll.
So the idea of detecting just direct (first-call) recursion is not enough, we need quite literally the equivalent of the call stack view in the debugger: a way to tell if a call (no matter how many async calls later) is a descendant of itself.
Thus, this question is not limited to just MutationObserver, as it necessarily involves a generic way to detect async calls descendent of themselves in the call tree. You can replace MutationObserver with any async event, really.
Explanation of the example above: in the example, the mutationobserver is triggering the bar animation on #foo whenever #foo is not .bar. However, the contents has an transitionend handler that sets #foo to .bar-final which triggers a vicious self-recursion chain. We would like to discard reacting to the #foo.bar-final change, by detecting that it's a consequence of our own action (starting the animation with #foo.bar).
One possible workaround for this could be to stop the mutation observer when one mutation is being fired
mutationObserver=new MutationObserver(handler);
mutationObserver.observe(window.document,{
attributes:true,
characterData:true,
childList:true,
subtree:true
});
// Trigger a MutationObserver change
document.getElementById('foo').setAttribute('class','bar');
document.getElementById('foo').setAttribute('class','');
function handler(){
console.log('Modification happend')
mutationObserver.disconnect();
// Trigger a MutationObserver change
document.getElementById('foo').setAttribute('class','bar');
document.getElementById('foo').setAttribute('class','');
mutationObserver.observe(window.document,{
attributes:true,
characterData:true,
childList:true,
subtree:true
});
}
See the JS fiddle
https://jsfiddle.net/tarunlalwani/8kf6t2oh/2/
The way I have done this in the past is to create semaphore to flag when an async function has already been called and is waiting execution on the event loop.
Here is a simple example for requestAnimationFrame
import raf from 'raf'
import State from './state'
let rafID
function delayedNotify () {
rafID = null
State.notify()
}
export default function rafUpdateBatcher () {
if (rafID) return // prevent multiple request animation frame callbacks
rafID = raf(delayedNotify)
}
In this case once you call the function, all future calls will be ignored until the first one is executed. Think of it as an async throttle function
For more complex scenarios then another solution might be this project
https://github.com/zeit/async-sema
From what I gather from reading your comments, if action A triggered action B asynchronously, you want to be able to tell where action A was done (in general, not just in a mutation observer). I don't think there's any trick built into JavaScript to do this like it seems you're looking for, however, if you know exactly how your JavaScript works, you can track this information. Job queues in JavaScript are FIFO by definition, the event loop queue also works this way. That means you can store information corresponding to a specific event in an array at the same time you're doing the action which triggers the event, and be confident that they're getting processed in the same order as the array. Here's an example with your mutation observer.
const
foo = document.getElementById('foo'),
mutationQueue = [];
function handler(){
console.log('handler');
const isHandling = mutationQueue.shift();
if(isHandling){
console.log('Re-entry!');
// Throttle/debounce and completely different handling logic
return;
}
setTimeout(()=> {
foo.setAttribute('class','bar');
foo.setAttribute('class','');
mutationQueue.push(true);
}, 1000 * Math.random());
}
mutationObserver=new MutationObserver(handler);
mutationObserver.observe(foo, {
attributes:true,
characterData:true,
childList:true,
subtree:true
});
function randomIntervals() {
setTimeout(()=>{
foo.setAttribute('class','bar');
foo.setAttribute('class','');
mutationQueue.push(false);
randomIntervals();
}, 1000 * Math.random())
}
randomIntervals();
<div id='foo'></div>
You have to make sure you add the appropriate value to the array at every point in your code that is going to trigger your async handler, or the entire thing will be messed up. I've never done this myself, I just thought of it for this question, but it seems pretty easy to do wrong. However, I fear this may be the only way to do what you want in general.

Javascript - How to fire an event handler immediately rather than queue it

Usually when a javascript event is fired from the code (as far as I know) the event handler is added to a queue, and will only be run after the current code.
(see, e.g., https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop)
Now, I have a call to element.blur(), and I want to run the function associated to the blur event imediately, and just after that would execution resume.
I could just call the function, but it was defined by a framework (ionic) and I do not want to mess with its internals, so ideally, I would retrieve the function programatically rather than know its name.
Is is possible? How?
You may call the associated function directly. However, therefore it needs to be in scope:
function onblur(){
this.style.color="blue";
}
document.getElementById("someelem").addEventListener("blur",onblur);
If you want to call it directly:
onblur.call(document.getElementById("someelem"));

How to achieve multiple event fires & events memory (features of events & promises combined)

My requirements
Because of the asynchronous architecture of my applications I am looking for an 'event' system which has the following two two properties:
The events should be able to fire multiple times (possible with events, but not with promises)
When I start listening for an event that has already been fired, I want the listener to fire once immediately (as with promises)
The reason for 1. is that there are a lot of events (e.g. the updating of certain data) that I want to be able to fire multiple times. But I would like to combine this with 2. so that if an event has already fired upon adding the listener, this listener gets called immediately. This is because I'm not always sure (and I don't want to be sure) which piece of code gets run first.
My 'solution'
I have thought up the following solution. I'm using this in an AngularJS application therefore the AngularJS context, but the question is applicable for Javascript in general. Note that I simplified the code.
app.controller('AppCtrl', function(CustomEventEmitter){
// Broadcast an event. No listener added so nothing happens
CustomEventEmitter.broadcast('event');
// Add the event listener. Because the event allready fired, the listener gets called immediatly
CustomEventEmitter.on('event', function(){
console.log('Event emitted');
});
// Broadcast an other event
CustomEventEmitter.broadcast('event');
});
app.service('CustomEventEmitter', function(){
var
listeners = {},
memory = [];
this.broadcast = function(name){
// The normal broadcasting of the event to the listener
if(listeners[name]) {
listeners[name].forEach(function(listener){
listener();
});
}
// Push the event into the 'memory'
memory.push(name);
};
this.on = function(name, listener){
// The normal adding of the listener
if(!listeners[name]) {
listeners[name] = [];
}
listeners[name].push(listener);
// If an event is already in memory, call the listener
if(memory.indexOf(name) !== -1) {
listener();
}
};
});
My questions
My questions are these:
What is the 'best practice' solution for my requirements?
What do you think of my 'solution'?
Am I missing something completely obvious?
The reason for the last question is that it seems to me that this is a very common design paradigm but I seem unable to find the best way to solve this in simple and concise way.
Note
I understand this can be solved with the adding of extra code (e.g. before adding the listener, check in an other way if the event you are going to listen for already happened) but this is not what I'm looking for.
A "property" from bacon.js does exactly what you are asking for. This falls under the broader category of functional reactive programming (FRP). The most popular two libraries for this in JavaScript are probably
bacon.js
Reactive Extensions
Both of which provide the specific tool you're asking for, along with a vast array of alternatives.

Calling V8 function causes access violation

I have a global event manager, allowing you to listen with lambdas to string event names.
// somewhere in the ModuleScript class
Event->Listen("WindowResize", [=]{
// ...
});
Now, I want to register to events from JavaScript, too. Therefore, I wrote this callback.
v8::Handle<v8::Value> ModuleScript::jsOn(const v8::Arguments& args)
{
// get pointer to class since we're in a static method
ModuleScript *module = (ModuleScript*)HelperScript::Unwrap(args.Data());
// get event name we want to register to from arguments
if(args.Length() < 1 || !args[0]->IsString())
return v8::Undefined();
string name = *v8::String::Utf8Value(args[0]);
// get callback function from arguments
if(args.Length() < 2 || !args[1]->IsFunction())
return v8::Undefined();
v8::Handle<v8::Function> callback =
v8::Local<v8::Function>::Cast(args[1]->ToObject());
// register event on global event manager
module->Event->Listen(name, [=]{
// create persistent handle so that function stays valid
// maybe this doesn't work, I don't know
v8::Persistent<v8::Function> function =
v8::Persistent<v8::Function>::New(args.GetIsolate(), callback);
// execute callback function
// causes the access violation
function->Call(function, 0, NULL);
});
return v8::Undefined();
}
When the event is triggered, the application crashes with a access violation. My thoughts are that either the function object isn't valid at this time anymore, or it is a JavaScript scope issue. But I couldn't figure it out.
What causes the access violation and how to overcome it?
I believe there are several potential issues here.
First, you're not using a persistent handle to hold the JavaScript function after ModuleScript::jsOn() terminates. By the time your event handler is invoked, the function might be gone. Consider making callback a persistent handle.
Second, your event handler needs to enter an appropriate V8 context before calling the JavaScript function. Depending on your architecture, explicitly locking and entering the V8 isolate may be required as well.
Third (and this may not be an issue in your specific scenario), you need to manage the lifetime of the V8 isolate. If your event manager fires events on background threads, you have to make sure your event handler somehow prevents the isolate from being disposed from another thread. Unfortunately this is one area where the V8 API doesn't provide much help.
Fourth, to prevent a leak, your event handler should dispose the persistent function handle after invoking the function.
Good luck!

Categories

Resources