I have a service which gets data from the back end which returns as a promise. Once I resolve the promise I maintain the data within the service, but I'd like to get that saved data as many times as I want as soon as it is available.
module.factory
var dataStash = {};
var service = {
getListData : getListData,
returnDataByListname: returnDataByListname
};
return service;
function getListData(listName){
//...get the data corresponding to this list
).then(setData);
function setData (data, listName){
//store the data from the resolved promise as an
//object attribute with name same as the list name
dataStash.listName = data;
}
}//end getListData
function returnDataByListname(listName){
//how best to do this??
return dataStash.listName;
}
Everything works fine but since I'm not returning the data as soon as the promise is resolved, but rather just saving it in the service, there is the issue of how soon I can call returnDataByListname. Since I don't know when the promise will be resolved and the data made available, how can I set up returnDataByListname so that it returns the data as soon as it is available after being called?
If you're primarily caching data, then returnDataByListname could return a promise:
function returnDataByListname(listName){
return promiseStash[listName] || promiseStash[listName] = getListData(listName);
}
Then callers could call returnDataByListname('foo').then(foo => console.log(foo)); repeatedly, yet getListData('foo') would only be called once.
Related
I am beginner at node and firebase and I don't get how to get the right value for the output here.
In the console i get the right value but the return value is undifiend.
function Emoji() {
var ref = firebase.database().ref('users').child('9ifrkEw7YjSRGtiaFFHT1rzieDA2');
this.getEmoji = function () {
ref.once('value').then(function (snapshot) {
console.log(snapshot.val().emoji); //displays the value from database
return String(snapshot.val().emoji); //displays undefiened
});
}
}
Your return statement is actually just returning from the anonymous function that you passed to then(). It's not returning from this.getEmoji. When you call getEmoji, it's going to return immediately, and some time later your database query will generate a result. The database query is asynchronous, so you should probably make getEmoji asynchronous as well by returning a promise that's resolved with the result of your query. The caller of getEmoji can use that promise's then() method to receive the result.
Suppose we have only one promise object like below.
var myPromise = $.get(url1);
myPromise.done(function(data){
console.log(data);
});
We are able to access the data from the promise object. Now suppose, we have multiple promise objects resolved via $.when
var multiplePromises = $.when($.get(url1),$.get(url2),$.get(url3));
multiplePromises.done(function(){
});
The above requirement has to be satisfied, that is, only if all the get requests completes, the done part should get executed. But how do I individually get the data response from each get to work with them in the $.when.done() method?
You get them as arguments.
function get(what) {
return $.when(what)
}
$.when(get(1), get(2), get(3)).done(function(first, second, third) {
console.log(first, second, third)
})
<script src="https://unpkg.com/jquery#3.2.1/dist/jquery.js"></script>
So I'm struggling with this a couple days and I have found a solution for this but I feel like this isn't the good one.
I currently have the following. I don't like it because I'm nesting Promises within promises. I don't know if this is fine but it doesn't look like it.
What I'm trying to accomplish with this code is first to check the cache database for a value if it is not there then I'll check the real database.
Any tips/tricks/pointers/comments on how to do this more elegantly?
var getData = function() {
var cancel = false
var cache = db.getFromCache(query)
.then((data) => {
// Check if data is up to date
if (uptodate) {
return Promise.resolve(data)
}
cancel = true
})
return cache
.then(() => {
if (cancel)
return db.getFromDatabase().then( //code)
}
}
ps: this code may or may not run I just made it quickly for this question. I can't past the real code here
When you're in a .then() handler, you can do anyone of the following:
Return a value - that value becomes the resolved value of the parent promise. So, there is no need to return Promise.resolve(value). You can just return value.
Return a promise - When you return a promise, it is chained to the parent promise and the parent promise will not resolve until this new promise resolves and the resolved value of this returned promise will become the resolved value of the parent promise.
Throw an Exception - If a .then() handler throws, that exception is automatically caught by the promise infrastructure and is turned into a rejection so throw err works similarly to return Promise.reject(err).
As such, when you're in your .then() handler, you can just check to see if the cache data is valid and, if so, just return it. Otherwise, return a new promise to get the data.
var getData = function() {
return db.getFromCache(query).then((data) => {
// Check if data is up to date
if (uptodate) {
// return cached data, will be resolved value of promise
return data;
} else {
// get data from db, return promise that will be chained
return db.getFromDatabase();
}
})
}
getData().then(...)
Your code is way more complicated than need be:
You don't need Promise.resolve(). You can just return the value.
You don't need the cancel variable at all. You can do all your work inside the first .then() handler.
You don't need the second .then() handler.
Promises support chaining, which means that a promise can return another promise, and this one can return another one, and so on.
According to MDN:
You can pass a lambda (anonymous function) to then and if it returns
a promise, an equivalent Promise will be exposed to the subsequent
then in the method chain.
When a value is simply returned from within a then lambda, it will
effectively return Promise.resolve().
This mean that in the then block, you can check if the data is up to date in the cache. If the data is fresh return it, and the value will be wrapped in a new promise. If the data is stale, you can return the call getFromDatabase(), which returns promise:
const getData = (query) => db.getFromCache(query)
.then((data) => isUpToDate(data) ? data : db.getFromDatabase(query));
getData().then(/** code **/);
Returning from a promise wraps the returned data with a new promise, so you can manipulate the data, and return it, and it will be wrapped by a promise automatically:
db.getFromDatabase().then((data) => data.map(/** some code **/)); // result will be wrapped in a promise.
I'm creating a factory to take a userId from one page, make a call to a REST API, and return the results on the following view. My initial attempts were largely taken from this answer but - unsurprisingly - I keep getting caught in a situation where the doesn't respond in time and the get() method returns an empty array.
Here's the factory itself
app.factory('GetMessages', function() {
var messages = []
function set(userId) {
Restangular.all('/api/messages/').getList({'_id': userId}).then(function(docs){
messages = docs
})
}
function get() {
return messages;
}
return {
set: set,
get: get
}
});
For what it's worth I'm having no trouble getting the userId into the factory as it's just passed in on a function like this
view:
<a ng-click='passToFactory(message.user.id)' href='/home/inbox/reply'>Reply</a>
controller:
$scope.passToFactory = function(id) {
GetMessages.set(id);
};
and the controller for the following view is just
$scope.messages = GetMessages.get()
The issue I'm having is that after the factory returns the empty set no further changes from the factory are recognized (even though after time elapses it does get the proper response from the API) and $scope.messages remains empty.
I've attempted to move the API call to the get method (this hasn't worked as the get method often does not get the userId in time) and I can't find a way to use a promise to force get() to wait on set() completing.
I'd prefer to keep using Restangular in the eventual solution but this is a small thing that has taken too much time so any fix works.
I'm fairly new to Angular so I'm sure there's something totally obvious but right now I'm just lost. Thanks.
The race condition that you have is that the function inside the .then method is executed asynchronously after the call to the set function. If the get function executes before the $q service fulfills the promise, the get function returns an empty array.
The solution is to save the promise and chain from the promise.
app.factory('GetMessages', function() {
var promise;
function set(userId) {
promise = Restangular.all('/api/messages/').getList({'_id': userId});
}
function get() {
return promise;
}
return {
set: set,
get: get
}
});
In your controller, chain from the promise.
GetMessages.get.then( function (docs) {
$scope.messages = docs;
}) .catch ( function (error) {
//log error
};
For more information on chaining promises, see the AngularJS $q Service API Reference -- chaining promises.
You are breaking the reference to the original messages array when you reassign it.
Try:
Restangular.all('/api/messages/').getList({'_id': userId}).then(function(docs){
messages.concat(docs) ; // keep same array reference
});
Simple example to explain why it isn't working
var arr = [];
var x = arr;
arr = [1,2,3]; // is now a different array reference
console.log(x); // is still empty array. x !== arr now
cherlietfl is right.
The problem is that you break the reference to the messages array since you assign a new array to messages inside your get function. But concat is doing this as well.
Try this:
Restangular.all('/api/messages/').getList({'_id': userId}).then(function(docs){
messages.splice(0, messages.length); // clear the array
messages.push.apply(messages, docs); //add the new content
});
Try assigning you function to the scope. Then call that function in the model. Like so:
// controller
$scope.getMessages = GetMessages.get;
View:
<div ng-repeat="message in getMessages()"></div>
This way when the request call finishes and the digest cycle goes through the watchers again, the get function will be called and you will get your messages.
My service needs to retrieve a value asynchronously, but once I have it, I'd like to used a cached version of the value.
When two controllers call this service, I'd expect the first one to cache the retrieved value and the second one to use the cached value, but according to the log, I never find a cached value. When this runs, I see a log message that shows the value being cached, then, when I follow an angular route to a different controller, I do not see that the service finds the cached value. Why does it not run according to my expectation**?**
angular.module('myApp.services').factory('Config', function() {
var Config = { };
Config.currentYear = function() {
if (Config._currentYear) {
// sadly, we never execute here
console.log("returning cached year");
return Parse.Promise.as(Config._currentYear);
}
return Parse.Config.get().then(function(config) {
console.log("caching year");
Config._currentYear = config.get("currentYear");
return Config._currentYear;
});
};
return Config;
});
A couple notes: (1) I named the cached attribute _currentYear, adding the underscore to avoid colliding with the function name. Not sure if I need to do that. (2) I return a fulfilled promise when the value is cached, so the function always returns a promise...also not sure if that's needed, but figure it can't hurt.
Instead of caching the data, why don't you just cache the promise and return it. When you cache the data, you are setting the data Config._currentYear only within the success callback and there are chances that other subsequent call(s) happening before the success callback is run. So you end up making the same call again. You can easily see this when you have calls made to the same service method from different controllers which are instantiated, by their presence on the same template. Caching a promise upfront will avoid these issues.
angular.module('myApp.services').factory('Config', function() {
var config; //Just use to save the promise
Config.currentYear = function() {
/*If there is already a call made before return the promise else
make the actual call and store the promise in the variable.*/
return config || config = Parse.Config.get().then(function(config) {
return config.get("currentYear");
});
};
});