AngularJS 'this' issue when using anonymous functions and $q promises - javascript

So we all know that 'this' is a tricky keyword in JavaScript, and anonymous functions and AngularJS promises make it even trickier.
QUESTION (TL&DR Version)
What is the right (and angular) way to allow promise callbacks
to use the same "this" as the service that initiated the request?
See this fiddle for an example:
http://jsfiddle.net/tpeiffer/CFD3e/
All of these methods on the controller calls off to the Tier1Service. Tier1Service then calls off to the WorkerService to get data. When the data is loaded, it returns said data via a promise to the Tier1Service. The data returned gets set into the Tier1Service_data property.
Alternate 3 is clean and it works, but it feels like there has to be a better way.
Alternate 4 is also very clean and it works, but again it seems wrong.
Now what I would REALLY like is for $q's promise to do all of this for me. :)
Here is the relevant code:
// App.js
angular.constructor.prototype.call = function (scope, func) {
return function () {
func.apply(scope, arguments);
};
};
// Tier1Service
get coolData() {
return this._data;
},
set coolData(val) {
this._data = val;
},
doWorkAlt1: function () {
mySubWorkerService.someData.then(function (data) {
// FAILS because 'this' is the window,
// not the service
if (data) this._data = data;
});
},
doWorkAlt2: function () {
mySubWorkerService.someData.then((function (data) {
// FAILS because data is undefined because
// the function is wrapped in an anonymous
// function
if (data) this._data = data;
}).call(this));
},
doWorkAlt3: function () {
// WORKS because I keep track of the instance
var instance = this;
mySubWorkerService.someData.then(function (data) {
if (data) instance._data = data;
});
},
doWorkAlt4: function () {
// WORKS because I keep pass 'this' around
mySubWorkerService.someData.then(angular.call(this, function (data) {
if (data) this._data = data;
}));
}
// WorkerService
get someData() {
var deferred = $q.defer();
deferred.resolve('i got back data!!');
return deferred.promise;
}

You should be able to use Function.bind to achieve the result you want:
doWork: function () {
mySubWorkerService.someData.then((function(data) {
//this now refers to whatever it referred to in the doWork function
}).bind(this));
}
Do note however that bind is not available in older browsers. However it's very easy to patch it in to the prototype manually if necessary.

Related

Chrome Extension | Is there any way to make chrome.storage.local.get() return something?

in my chrome extension I need to use chrome storage. In my background script first I create an object and add it to chrome storage and then I want to get my object from there and to be returned. Something like that:
...
var obj = {};
chrome.storage.local.set(obj, function () { });
...
var data = getData(obj); // I want my object to be returned here
var returnedData = null;
function getData(obj) {
chrome.storage.local.get(obj, function(result) {
returnedData = result; // here it works, I can do something with my object
});
return returnedData; // here it doesn't work
}
As far as I understood from here chrome.storage.local.get is asynchronous with its consequences. But is there any way how to get something from chrome storage and make it to be returned? I mean maybe I should wrap chrome.storage.local.get in another function or so?
Many thanks in advance!
If you want to stay away from global variables and you're okay with modern browser requirements, then you can implement a native JavaScript Promise object. For example, here's a function that returns the stored data for a single given key:
function getData(sKey) {
return new Promise(function(resolve, reject) {
chrome.storage.local.get(sKey, function(items) {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError.message);
reject(chrome.runtime.lastError.message);
} else {
resolve(items[sKey]);
}
});
});
}
// Sample usage given this data:
// { foo: 'bar' }
getData('foo').then(function(item) {
// Returns "bar"
console.log(item);
});
If you need support for IE11 and below, then you'll have to turn to a library like jQuery.
No it's not possible
But there are several ways around this problem
Do everything you want to do with the data returned from .get() inside the callback (or start it from there using function calls). This is what #wernersbacher posted
Take a look at deferreds (jQuery or Q libraries). A deferred's promise can be returned from getData. Inside the .get() callback, you can resolve the deferred. Outside of getData you can use .then() to do something after the deferred resolved
Something like this
function getData(obj) {
var deferred = $.Deferred();
chrome.storage.local.get(obj, function(result) {
deferred.resolve(result);
});
return deferred.promise();
}
$.when(getData(obj)).then(function(data) {
// data has value of result now
};
You have to do it like that:
var returnedData = null;
function setData(value) {
returnedData = value;
}
function getData(obj) {
chrome.storage.local.get(obj, function(result) {
setData(result); // here it works, I can do something with my object
});
return; // here it doesn't work
}
..because you tried to return a value which did not get read from storage yet, so it's null.
Update with Manifest V3 :
Now chrome.storage.local.get() function returns a promise that you can chain or can await in an async function.
const storageCache = { count: 0 };
// Asynchronously retrieve data from storage.local, then cache it.
const initStorageCache = chrome.storage.local.get().then((items) => {
// Copy the data retrieved from storage into storageCache.
Object.assign(storageCache, items);
});
Note : You must omit the callback paramter to get the promise.
Reference : https://developer.chrome.com/docs/extensions/reference/storage/#:~:text=to%20callback.-,get,-function
You need to handle it with callback functions. Here are two examples. You use a single function to set, however you create a separate function for each "On Complete". You could easily modify your callback to pass additional params all the way through to perform your needed task.
function setLocalStorage(key, val) {
var obj = {};
obj[key] = val;
chrome.storage.local.set(obj, function() {
console.log('Set: '+key+'='+obj[key]);
});
}
function getLocalStorage(key, callback) {
chrome.storage.local.get(key, function(items) {
callback(key, items[key]);
});
}
setLocalStorage('myFirstKeyName', 'My Keys Value Is FIRST!');
setLocalStorage('mySecondKeyName', 'My Keys Value Is SECOND!');
getLocalStorage('myFirstKeyName', CallbackA);
getLocalStorage('mySecondKeyName', CallbackB);
// Here are a couple example callback
// functions that get executed on the
// key/val being retrieved.
function CallbackA(key, val) {
console.log('Fired In CallbackA: '+key+'='+val);
}
function CallbackB(key, val) {
console.log('Fired In CallbackA: '+key+'='+val);
}

Filtering and $http promises in Angular

I've got a problem with filtering data from JSON file, which is an array of 20 objects.
in my factory I have these two functions.
function getData() {
return $http
.get('mock.json')
.success(_handleData)
.error(_handleError);
}
function _handleData(data) {
var filteredData = _filterData(data, "name", "XYZ");
console.log('filteredData', filteredData);
return filteredData;
}
and here console.log("filteredData") shows only filtered elements (i.e. 3 out of 20);
next - in a service I've got this one on ng-click:
var filterMe = function () {
DataFactory
.getData(_address)
.success(_handleServiceData );
}
where
var _handleServiceData = function (data) {
filtered = data;
};
the thing is - why the 'data' in _handleServiceData shows all of the elements instead of these previously filtered?
edit: here's the plunk - results are logged in console
Because the filteredData you return from _handleData function is not passed to the success callback you attach in filterMe function. That's because you attach that callback on the very same promise, since success function doesn't create new promise like the then method does. So to solve this modify your code like this:
function getData() {
return $http
.get('mock.json')
.then(_handleData, _handleError); //use "then" instead of "success"
}
Then in filterMe function:
var filterMe = function () {
DataFactory
.getData(_address)
.then(_handleServiceData );
}
Because promises are asynchronous, and you seem to return the value of filtered to your caller before it could be assigned.
You should be doing
function getData() {
return $http
.get('mock.json')
.then(_handleData); // then (for chaining), not success!
}
var filterMe = function () {
return DataFactory
// ^^^^^^ return a promise, not assign globals in async callbacks
.getData(_address)
.catch(_handleError); // I assume you want to deal with errors only in the end
}

How to return data in a factory method within Angular?

I'm an ionic/Angular n00b and I having trouble wrapping my head around how to do this.
I have a factory defined as such:
angular.module('starter.services', [])
.factory('Calendars', function () {
var calendars;
var success = function(message) {
calendars = message;
return calendars;
};
var error = function(message) {alert("Error: " + message)};
window.plugins.calendar.listCalendars(success,error);
return {
all: function() {
return calendars;
},
get: function(calendarId) {
return calendars[calendarId];
}
}
});
And I'm trying to retrieve the calendars within my controller like this:
.controller('CalendarsCtrl', function($scope,Calendars) {
$scope.calendars = Calendars.all();
})
The factory method is being called but the results are not available until the 'success' callback is invoked so the CalendarsCtrl is always undefined.
How to solve this?
Edit - I've corrected the call within the controller. The same issue remains though, that the function does not return results until the success callback.
You will have to use a promise.
First add the dependency $q
.factory('Calendars', function ($q) {
then in all() you do this
all: function() {
var deferred = $q.defer();
window.plugins.calendar.listCalendars(function(data) {
deferred.resolve(data);
}
,function(error) {
deferred.reject(error); // something went wrong here
});
return deferred.promise;
now this will make the return after the data has been resolved (no more undefined).
One last thing, now when you get the data back at your controller you do this
var promise = Calendars.all();
promise.then(function(data) {
console.log('Success: you can use your calendar data now');
}, function(error) {
console.log('Failed for some reason:' + error);
});
You can read some more about promises here: https://docs.angularjs.org/api/ng/service/$q
I know it's hard to grasp the first time.
Angular factories are returning an object, in order to call their methods you must call them with Calendar.all() which will invoke the inner function.

To whom return is returning the value (javascript)?

I saw following code in the HotTowel project. In the following code, callback method for then returns value return vm.messabeCount = data;
(function () {
'use strict';
function dashboard(common, datacontext) {
vm.messageCount = 0;
function getMessageCount() {
return datacontext.getMessageCount().then(function (data) {
/******* Here ********/
return vm.messageCount = data;
});
}
}
})();
Am wondering why & to whom it's returning value. Is it some standard practice? Can't the code be just.
return datacontext.getMessageCount().then(function (data) {
vm.messageCount = data;
});
Or
return datacontext.getMessageCount().then(function (data) {
vm.messageCount = data;
return;
});
getMessageCount is a function returning a promise object. then method of this promise returns another promise again. It makes possible to chain multiple then parts. Each then(function() { ... }) has an ability to modify a data to be passed to the next then invocation. So this construction:
return datacontext.getMessageCount().then(function(data) {
return vm.messageCount = data;
});
means to modify a data passed to resolve callbacks. Without this return success functions would be resolved with undefined value, while we need it to be resolved with data.

Chained promises and preserving `this`

I have a click handler that needs to make several async calls, one after another. I've chosen to structure these calls using promises (RSVP, to be precise).
Below, you can see the clickA handler, inside the controller (it's an Ember app, but the problem is more general, I think):
App.SomeController = Ember.Controller.extend({
actions: {
clickA: function() {
var self = this;
function startProcess() {
return makeAjaxCall(url, {
'foo': self.get('foo')
});
}
function continueProcess(response) {
return makeAjaxCall(url, {
'bar': self.get('bar')
});
}
function finishProcess(response) {
return new Ember.RSVP.Promise(...);
}
...
startProcess()
.then(continueProcess)
.then(finishProcess)
.catch(errorHandler);
}
}
});
It looks great, but now I have to add a second action that reuses some of the steps.
Since each of the inner functions needs to access properties from the controller, one solution would be to make them methods of the controller:
App.SomeController = Ember.Controller.extend({
startProcess: function() {
return makeAjaxCall(url, {
'foo': this.get('foo')
});
},
continueProcess: function(response) {
return makeAjaxCall(url, {
'bar': this.get('bar')
});
},
finishProcess: function(response) {
return new Ember.RSVP.Promise(...);
},
actions: {
clickA: function() {
this.startProcess()
.then(jQuery.proxy(this, 'continueProcess'))
.then(jQuery.proxy(this, 'finishProcess'))
.catch(jQuery.proxy(this, 'errorHandler'));
},
clickB: function() {
this.startProcess()
.then(jQuery.proxy(this, 'doSomethingElse'))
.catch(jQuery.proxy(this, 'errorHandler'));
}
}
});
So, my question is: is there a better way? Can I get rid of all those jQuery.proxy() calls somehow?
A solution would be to use a better promise library.
Bluebird has a bind function which lets you bind a context to the whole promise chain (all functions you pass to then or catch or finally are called with this context ).
Here's an article (that I wrote) about bound promises used like you want to keep a controller/resource : Using bound promises to ease database querying in node.js
I build my promise like this :
// returns a promise bound to a connection, available to issue queries
// The connection must be released using off
exports.on = function(val){
var con = new Con(), resolver = Promise.defer();
pool.connect(function(err, client, done){
if (err) {
resolver.reject(err);
} else {
// the instance of Con embeds the connection
// and the releasing function
con.client = client;
con.done = done;
// val is passed as value in the resolution so that it's available
// in the next step of the promise chain
resolver.resolve(val);
}
});
// the promise is bound to the Con instance and returned
return resolver.promise.bind(con);
}
which allows me to do this :
db.on(userId) // get a connection from the pool
.then(db.getUser) // use it to issue an asynchronous query
.then(function(user){ // then, with the result of the query
ui.showUser(user); // do something
}).finally(db.off); // and return the connection to the pool
I may be missing something, but would this solve your issue?
actions: (function() {
var self = this;
function startProcess() { /* ... */ }
function continueProcess(response) { /* ... */ }
function finishProcess(response) { /* ... */ }
function doSomethingElse(response) { /* ... */ }
/* ... */
return {
clickA: function() {
startProcess()
.then(continueProcess)
.then(finishProcess)
.catch(errorHandler);
},
clickB: function() {
startProcess()
.then(doSomethingElse)
.catch(errorHandler));
}
};
}());
Just wrap the actions in an IIFE, and store the common functions there, exposing only the final functions you need. But I don't know Ember at all, and maybe I'm missing something fundamental...
Browsers have a "bind" method on all functions. It's also easy to create a pollyfill for Function#bind.
this.startProcess()
.then(this.continueProcess.bind(this))
.then(this.finishProcess.bind(this))
.catch(this.errorHandler.bind(this));
The jQuery.proxy method essentially does the same thing.

Categories

Resources