I am having a strange issue here I hope you all can help with.
Project Details
I am working on a simple pub/sub implementation for a larger application that includes a pubsub.subscribe_once() method. This method enables the creation of one-off subscriptions, meaning that a generic subscription is created, and then once the correct "publish" event fires and the subscription callback is run, the subscription deletes itself.
subscribe_once: function(topic, func) {
var sub = pubsub.subscribe(topic, func),
old_func = sub.func;
// rewrite our subscription's method to remove itself after invocation
sub.func = function() {
// call the original function
old_func.apply(this);
// remove subscription from topic
pubsub.unsubscribe(sub);
};
return sub;
}
Problem
I seem to be having some kind of issue with the memory flow of this process. (In order to best understand the following explanation I suggest you walk through the jsfiddle demo below as I go.) I create a subscribe_once('someevent') subscription, and then fire publish('someevent'). What you would expect to see when the publish method is invoked is that the topics hashtable contains a "someevent" key, which references an array of Subscription objects. And in fact, if you reference topics["someevent"], you see an array with a single Subscription. If, however, you reference topics you see the "someevent" key, but the array is empty!
By commenting out pubsub.unsubscribe(sub); the problem is eliminated, even though this function does not appear to be fired until after we run console.log(topics).
Further, this does not seem to be an issue with the way a given browser "threads" console.log; try console.log(topics, topics[topic], topics, topics[topic]) and you get the same result.
Demo: http://jsfiddle.net/4Ab6c/
Any help would be greatly appreciated! Thanks.
I'm still looking for some documentation to back me up, but I suspect the object display in the console is performing lazy evaluation on your topics object. I added console.log(topics) to the subscribe method after sub is pushed onto the array and I get the same result as your 'but not here' log. When I wrap the final line of your fiddle pubsub.publish('someevent') in setTimeout and I get the Object tree open before the publish callback runs, then it shows the subscription in the array and it stays that way even after the callback runs. If I don't open the object tree in the console before the callback runs then I see the empty array.
I will keep searching for a blog post or something that confirms lazy evaluation is occurring.
Just in case I haven't made it obvious enough, by lazy I mean, the console isn't gathering the details of the object until the tree view is clicked open in the console.
I am working in Chrome.
UPDATE
I have found similar behavior on Firefox as well. Firefox recognizes that there is one object in the array but if you don't drill down into the array before the publish even fires then the drill-down on the array will be empty.
I updated the fiddle from your comment:
http://jsfiddle.net/4Ab6c/2/
Please try this:
Run the fiddle and expand the object tree for the first console.log before the publish event fires, I set it to a five second timeout but you could make it longer or shorter depending on how quickly you can get down to the console and click the inspector open.
You should see the subscribe object in the array as expected.
Clear the console and run the fiddle again. This time do not open the object inspector until after the publish event has fired and all the code is done running.
Now when you open the object inspector of the first console.log you should not see the subscription event in the array.
UPDATE 2
Here is a much simpler fiddle that exhibits the same behavior:
http://jsfiddle.net/4Ab6c/3/
If you expand first before second shows up then you will get foo: bar. If you expand first after second shows up you will get foo: baz.
UPDATE 3
And, voila, another SO question seconds the motion on lazy evaluation.
Related
I work on an Angular (v13) project which uses ngx-translate/core for i18n. There are multiple languages (15 currently) configured. I find the following very strange behaviour (TranslateService is injected as this.translateService):
Calling this.translateService.getLangs() returns an array with the 15 language codes as expected. I can log this value or e.g. configure a dropdown select with these values.
However, I can do absolutely no array operations on this value, including indexing [0] (says undefined), .length (returns 0), or for-in or for-of. No ... operator to create a new array or object with the values, no Object.assign([], langs), no Array.from() - they just return empty arrays. Let alone .map(), .forEach(), etc.
typeof says object, Array.isArray() says true.
Some basic example code:
ngOnInit(): void {
console.log(
this.translateService.getLangs(),
this.translateService.getLangs().length,
this.translateService.getLangs()[0],
this.translateService.getLangs().includes('en'),
this.translateService.getLangs().includes('aa')
);
}
shows in the console:
[] 0 undefined false false
and if I expand the [] it shows the 15 codes (including 'en'), and length: 15.
Questions
Am I reading the ngx-translate documentation wrong (that what is there)?
Am I using it wrong somehow due to not understanding something?
Is this a bug in ngx-translate?
Is this a bug in JavaScript?
Is there any workaround?
Thank you to Amer for his comment pointing me in the right direction.
The problem occurs because this.translateService.getLangs() is called before languages have been loaded via this.translateService.addLangs(). The language values are obtained via a HttpClient.get() request, which returns an Observable and calls addLangs() once the results are received (refer Observable.subscribe(...)). (So no funny happenings, just a wrong understanding.)
The simple solution is to only do any operations on the array of languages in the complete callback of the Observable returned by HttpClient.get() (or use one of the other synchronization mechanisms available). This was a bug in the original code and has now been fixed.
The browser (Chrome) logging [] (which could then be expanded to the full array) was a confusion on my part. When the array is available at logging time, it is logged as expanded, e.g. [ "ar", "en", .... ]. (By the time I clicked on the expansion widget in the console, the array had obviously been loaded in the mean time, which enables it to be shown on expansion.)
I strongly advise against synchronization via timeouts as someone may wrongly read from Amer's comment, where it is suggested only for problem detection. One should always use one of the synchronization methods (Promises, Observables, events, etc. etc.) in final code to synchronize between asynchronous processes.
My first observable emits data concerning my user (from Firebase). On this data being emitted, I'd like to trigger a second call to Firebase, retrieving various extra user information from a distinct collection.
I can manage the two operations individually just fine, but obviously the latter call should only really take place after the first call has succeeded. I imagine this is something to do with mergeMap but I can't figure out quite how to do it. Should point out to the close/downvoter that I've, despite trying, not found similar questions that are both answered and present the solution in a way I can match to exactly what I'm failing to understand.
this._auth.userState$
.mergeMap(user => this._userMetaData.getData(user.uid))
.subscribe(data => {
console.log(data);
});
This is my initial effort. I was hoping that the console.log would return (in some fashion) the data from both calls. It just logs an empty array, instead.
Can anyone point me in the right direction here?
I see 2 different points worth to be highlighted.
1) Within the submit, you want to access the data returned by the 2 calls:
This can be accomplished in the following way
.mergeMap(user =>
this._userMetaData.getData(user.uid)
.map(data => ({user, data})
)
Here you basically create, via the last map, a return which is an object referencing both the user returned by the first call and the data returned by the second call
2) In the log you see an empty Array:
Here you need to look at your logic since, according to your current code, in the log you should see the result of the second call
I've encountered a very strange issue with jQuery: I previously used the data() method to get an object that is stored on the element, like this:
var player = $(el).data("ytPlayer");
However, suddenly, this is returning an undefined result.
Looking at the $(el) object during setup and initialization of the page, I see it in the list of properties:
and at this point, if I call $(el).data("ytPlayer") it works as expected and I get the object.
But later in the lifecycle of the page, when the call to data() fails, I see this in the list of properties for that element:
There's an empty object, and also you can see my desired object right below it, but I can't seem to get to it. If at this point I call $(el).data() with no parameters, I get an empty object as well.
Is there any way to trace where this empty object is coming from, and/or any way to force jQuery to ignore it to get to the object I need?
It turns out the best way (at least for me) to troubleshoot this problem was to remove all scripts until the call to data() worked again, then gradually add them back one at a time until it failed.
this allowed us to isolate the offending script and fix it.
I have an array called objs that holds all of my application objects. Objects get added and removed from this list depending upon what happens in the application.
I am having this problem where some objects disappear (or are overwritten) only sometimes. If I step through the add and remove functions, the app always runs as it should, however many times when it is run without the debugger, one or two objects that were added to the end of the list disappear from the list.
objects are added to the array like this:
this.objs[this.objs.length]=obj;
and are removed from the array like this:
for(var i=0;i<this.objs.length;i++)
if(this.objs[i]==obj)
return this.objs.splice(i,1);
I put this code at the end of my add and remove functions:
console.log("add! ");
console.log(this.objs);
Linked is an image of a console log during a session where an object dissapeared: http://ilujin.com/error.png
The first 4 objects in the list shown at the top should remain in the list throughout the session, but the object at index 3 (highlighted in red), gets overwritten by the next object that gets added (highlighted in blue).
The other weird thing is that the second list shown already has all of the changes (4 objects removed and 1 added), even though the remove function has only been called once and the add function not at all.
This makes me conclude that the problem is timing - if one add hasn't finished before the next add is called, the first one will be overwritten. And all of the console prints are the same because they all happen before the console can read and print.
Does this makes sense? For some reason I thought JS never ran parallel code and only moved on to a new function when the last function finished. Is the problem that I'm using the length of the objs list as the new index when I add to the list?
How can I fix this issue? I can't figure it out, and the debugger and console have proven useless.
Here is the app: http://iioengine.com/neuro/study2.htm
you only need to enter an id and see if the instructions pop up. If they do, than its working and refresh. If they don't, that means that the Text Object got overwritten.
You would really be better served by using Javascript's array methods.
Add to array:
this.objs.push(obj);
Remove from array:
this.objs.splice(this.objs.indexOf(obj), 1);
Also, note that splice edits the original array and returns the elements that have been removed. It's hard to tell from your limited code sample, but that might also be causing issues.
I'm creating a Firefox extension, in which I want to iterate through the Application.windows array and check if one of its elements is the same as Application.activeWindow.
The mentioned excerpt from my code looks like this:
for (var i in Application.windows) {
if (Application.windows[i]==Application.activeWindow) alert('debug');
// there was some more complex code than alert('debug'),
// but since it didn't work, I decided to try with an alert
}
Unfortunately, the 'debug' alert is never viewed. Thus I decided to try this code (with only one window opened):
// the following code runs in an event listener for window.onload
alert(Application.windows[0]);
alert(Application.activeWindow);
alert(Application.windows[0]==Application.activeWindow);
Firefox displayed 3 alerts: the first one was [object Object], the second one - [xpconnect wrapped fuelIWindow], and the last one (which didn't surprise me) said false. So it seems the objects I'm trying to compare have different types. How can I deal with this? Thanks in advance.
You have two problems.
The first is that XPConnect doesn't support array-valued properties, so when FUEL (or STEEL or SMILE) return an array, they're actually returning an nsIVariant of internal objects! On the other hand, single-valued objects return an XPConnect wrapper which hides the internal object.
The second is that each time you access windows or activeWindow, new internal objects are created, so even two calls to activeWindow return different objects.
The way around this is to avoid FUEL and enumerate the windows directly using the window mediator.