I've run into an issue where I'm unable to get the value I need from inside a callback that's passed to an instantiation of a JavaScript class.
let value;
const mutationObserver = new MutationObserver((mutations) => {
return values.map((mutation) => {
value = mutation.target.childElementCount;
return value;
})
})
console.log("value", value) <= undefined
const element = document.getElementsByClassName('pac-container')[0];
if (element) {
mutationObserver.observe(element, {
childList: true,
subtree: true
});
}
I'm using the MutationObserver because I'd like a way to know, in real-time, how the DOM is being mutated by the results from the googlePlaces API. Since I don't have control over that markup - this seems like the next best thing.
I have explored using the AutoCompleteService - but that would require us to re-implement the existing googlePlaces using the AutoCompleteService call, instead of the AutoComplete API endpoint.
The approach I'm using here is in perfect sync with the results.
I don't think I've run into before - and the difference here is that the value is set inside this instance of a class.
How do you allow the value retrieved inside the class to be defined outside the scope of the function inside the class where it's handled?
We're making the call to the googlePlaces API like this:
const autoComplete = new this.googleApi.places.Autocomplete(
this.props.inputRef,
INIT_OPTIONS
);
An object is returned after the call - and I can see the request being made - and there is new data after each search - but there is no clean JSON response - and the data is embedded in some optimized way deep in the object.
Related
I am trying to use the region variable inside the callback however there is no way I can get the value of it.
As you might be already aware I am instantiating a JS objects using TS and the callback is part of the definition of this JS object.
I am already being able to use JS in my Angular components using () => { in those methods where I need to use any reference to 'this' inside JS callbacks but for this particular case it does not work at all.
showRegion = (region: string) => {
var service = new JS.service();
var prov = new JS.provider(service, {
callback: (data) => {
// cannot read region variable
}
})
Thanks,
I have created a local class in a JavaScript file with following content:
class CustomChromeStorage {
//#region userName
get userName() {
let isCurrentValueSet = false;
chrome.storage.sync.get('userName', function (obj) {
this._userName = obj;
isCurrentValueSet = true;
});
while (true) {
if (isCurrentValueSet) {
return this._userName;
}
}
}
set userName(newValue) {
this._userName = newValue;
chrome.storage.sync.set({ 'userName': newValue }, function () {
});
}
remove_userName() {
this._userName = null;
chrome.storage.sync.remove('userName', function () {
});
}
//#endregion userName
My Idea to do such type of code is when I write somewhere else in my code like:
alert(new CustomChromeStorage().userName);
Then my code simply fetches username from chrome storage and show it via an alert. In order to fetch a value from chrome storage we need to provide a callback with as parameter the value. I know this is good practice for asynchronous process but it sometimes becomes cumbersome for me to handle all the callbacks.
I want that when I fetch value from chrome storage via my custom class to execute current code asyncronously. This is why I have written infinite while loop inside getter method of that property but the problem is when I try to alert username via custom chrome storage class my total program execution becomes hang.
The reason behind it is that I initially set isCurrentValueSet = false which never gets true inside while loop.
If anybody have any idea why it does not set to true inside while loop then please let me know.
The obj returned from sync.get is {userName: value} - use obj.userName.
The reason isCurrentValueSet doesn't get set to true is because the function is asynchronous - when the callback executes, it doesn't have access to the class variable isCurrentValueSet.
What you're trying to achieve is just wrong. It's a fact that storage requests are asynchronous for the good of the user and browser performance. You have to learn to design around it and it's easy enough when you get used to it.
You can retrieve multiple variables in one hit so if you have a section of code that needs several variables, just do:
chrome.storage.sync.get({a:"",b:"",c:0,d:[]}, function(result) {
a = result.a
b = result.b
c = result.c
d = result.d
your code
});
By passing an object in, you can request multiple variables and define defaults for if they don't yet exist in storage. Of course you don't have to extract the variables.
I am write a compatibility layer over a legacy library function whose internal signature looks like —
function legacyLibraryFunction(context) {
context.foo.bar = "data"
return context
}
The new system however, doesn't recommend assigning custom properties directly to context and instead recommends using the context.set() method.
How do I pass context from the new system to the old one so that context.foo="data" ends up called context.set('foo', data) instead?
I'm guessing I can use Object.defineProperty for this, but I don't really understand how.
Using a setter:
You can use a setter for this:
var wrappedContext = {
set foo(val) {
context.set('foo',val);
}
}
Note: I'm assuming context.foo instead of context.foo.bar because the second part of your question does not quite match the example code.
This will create an object (wrappedContext) that will have a foo property that will call context.set() when you assign a value to it. So then you can do:
legacyLibraryFunction(wrappedContext);
Using a proxy:
Since you're using node 6.6 you can also use a proxy:
var wrappedContext = new Proxy(context,{
set: function(obj, prop, value) {
obj.set(prop,value);
}
});
I am developing an AngularJS application and found the following behavior.
I have two functions in my service. The first function returns all the categories stored in the database and the second returns one category by its id.
Here is my service:
angular.module('categoriesRepository', [])
.service('categoriesRepository', ['$cordovaSQLite', 'sqliteHelper',
function ($cordovaSQLite, sqliteHelper) {
//this works - returns an array with all categories
this.getAll = function () {
var categories = [];
$cordovaSQLite.execute(sqliteHelper.getDb(),
"SELECT * FROM categories;")
.then(function (res) {
for (var i = 0; i < res.rows.length; i++) {
categories.push(res.rows[i]);
}
});
return categories;
}
//this works not - returns undefined
this.getById = function (id) {
var category;
$cordovaSQLite.execute(sqliteHelper.getDb(),
"SELECT * FROM categories WHERE id = ?;", [id])
.then(function (res) {
category = res.rows[0];
});
return category;
}
}]);
I know that I can use Angulars $q to run functions asynchronously, and use their values when they are done processing.
Why does the getById function return the category directly and the getAll wait until the array is filled?
EDIT
I had the getAll function posted wrong. There is no return statement before $cordovaSQLite.execute
UPDATE:-
After your question is updated.
In the first example your are creating an array first by doing var categories = [];and then returning this array before finishing your async call. When your async call completes it just pushes certain elements into the array thus not destroying the reference to the array (categories ) variable. When it is returned back if you will debug it you will find the function returning an empty array and later when the async call succeeds only then the array will be filled.
In the second example you are creating just a variable and then returning it before the async call finishes. But then the async call is finished you assign the variable to a new value. thus destroying the earlier reference.
Solution:-
Though not a preffered approach to make it work. you will have to maintain the category variable reference. for this you can use angular.copy OR angular extend
So the second part of your code should be like
this.getById = function (id) {
var category;
$cordovaSQLite.execute(sqliteHelper.getDb(),
"SELECT * FROM categories WHERE id = ?;", [id])
.then(function (res) {
angular.copy(res.rows[0], category);
//now the reference to the category variable
//will not be lost
});
return category;
}
Better Practice:-
The way you have been developing this application is wrong. Async calls should not be handled this way. I earlier asked a question just to clarify the way to handle the async calls and state inside the angular app, factories and controllers please have a look here. It provides two ways to handle the state and async calls. There might be many more practices out there but these two suit me best.
It is unfortunate that this approach appears to 'work' because it is caused by the modification of the returned array object "at some unspecified time" after it is returned.
In the usage the array is accessed/observed after1 it has been modified by the asynchronous call. This makes it appear to function correctly only because of the (accidental) asynchronous-later-than observation.
If the observation was prior to the actual completion of the SQLite operation - such as immediately after the getAll function call - it would reveal an empty array.
Both functions are incorrectly written and the first accidently releases Zalgo (or perhaps his sibling).
See How do I return the response from an asynchronous call? for more details.
1 Chrome's console.log can be confusing as it works like console.dir and thus may be showing the current value and not the value when it was invoked.
As stated already, this is a bad approach. You can't use result of your function immediately after it returns.
However I didn't see the answer to your exact question: why do they behave differently?
It happens because with an array you return a reference to an object (type Array). Later on you use same reference to modify contents of the object, i.e. push new items into the array.
However in second function you modify the reference itself. You make you local variable categories point to a new object. Thus old object (reference to which was returned to outer scope) remains untouched. To make it work the same way you should have written
category.row = res.rows[0];
You return the result of the execute in the first case, whereas you return the variable in the second case, which has most likely not been populated yet.
The situation was that I wanted to create an instance of a helper class, but that helper class required initialisation through external scripts, so it was inherently asynchronous. With
var obj = new myObj();
clearly an call to
obj.myMethod();
would yield undefined, as obj would either be empty or undefined until its methods and params were loaded by the external script.
Yes, one could restructure things to have a callback pattern and work with the new object within that, but it gets cumbersome and awkward when working with a large and varied API with many dynamic objects as I've been working with.
My question has been, is there any possible way to cleverly get around this?
I imagine the academically trained programmers out there have a name for this sort of approach, but I put it here in case it's not better written somewhere.
What I've done is modify my loader class to use a placeholder+queue system to instantly return workable objects.
Here are the components. Sorry that there are jQuery bits mixed in, you can easily make this a pure-JS script but I've got it loaded anyway and I'm lazy.
'Client' makes this request, where 'caller' is my handler class:
var obj = caller.use('myObj',args);
In Caller, we have
Caller.prototype.use = function(objname,args) {
var _this = this;
var methods = ['method1','method2'];
var id = someRandomString();
this.myASyncLoader(objname,function(){
var q = [];
if (_this.objs[id].loadqueue) {
q = _this.objs[id].loadqueue;
}
_this.objs[id] = new myRemotelyLoadedClass(args);
//realise all our placeholder stuff is now gone, we kept the queue in 'q'
_this.objs[id].isloaded = true;
//once again, the jquery is unnecessary, sorry
$.each(q,function(a,b){
_this.objs[id][b['f']](b['a']);
});
});
_this.objs[id] = _this.createPlaceholderObj(methods,id);
return _this.objs[id];
}
This function basically initiates the loader function, and when that's done loads a new instance of the desired class. But in the meantime it immediately returns something, a placeholder object that we're going to load with all of our remotely loaded object's methods. In this example we have to explicitly declare them in an array which is a bit cumbersome but liveable, though I'm sure you can think of a better way to do it for your own purposes.
You see we're keeping both the temporary object and future object in a class-global array 'objs', associated with a random key.
Here's the createPlaceholderObj method:
Caller.prototype.createPlaceholderObj = function(methods,id) {
var _this = this;
var n = {};
n.tempid = id;
n.isloaded = false;
$.each(methods,function(a,methodCalled){
n[methodCalled] = function(){
_this.queueCall(id,methodCalled,arguments);
}
});
return n;
}
Here we're just loading up the new obj with the required methods, also storing the ID, which is important. We assign to the new methods a third function, queueCall, to which we pass the method called and any arguments it was sent with. Here's that method:
Caller.prototype.queueCall = function(id,methodName,args) {
if (this.objs[id].isloaded == true) {
this.objs[id][methodName](args);
} else {
if (this.objs[id].loadqueue) {
this.objs[id].loadqueue.push({'f':methodName,'a':args});
} else {
var arr = [{'f':methodName,'a':args}];
this.objs[id].loadqueue = arr;
}
}
}
This method will be called each time the client script is calling a method of our new object instance, whether its logic has actually been loaded or not. The IF statement here checks which is the case (isloaded is set to true in the caller method as soon as the async function is done). If the object is not loaded, the methodName and arguments are added to a queue array as a property of our placeholder. If it is loaded, then we can simply execute the method.
Back in the caller method, that last unexplained bit is where we check to see if there is a queue, and if there is, loop through it and execute the stored method names and arguments.
And that's it! Now I can do:
var obj = caller.use('myObj',args);
obj.someMethod('cool');
obj.anotherMethod('beans');
and while there might be a slight delay before those methods actually get executed, they'll run without complaint!
Not too short a solution, but if you're working on a big project you can just put this in one place and it will pay many dividends.
I'm hoping for some follow-ups to this question. I wonder, for example, how some of you would do this using a deferred-promise pattern? Or if there are any other ways? Or if anyone knows what this technique is called? Input from JS whizzes much appreciated.