$http promise is not waiting to finish loop inside of it - javascript

I was trying to run AngularJS forEach loop inside a $http request where as promise is not waiting to complete the loop and before that its returning.
Please find my code below:
return $http({
method: 'GET',
url: $rootScope.baseUrl + 'test/test/test/test',
headers: {
"token": token
}
})
.then(function(responce) {
var dashboardCheck = function() {
var defer = $q.defer();
angular.forEach($rootScope.getDashboardDetails.dashboardList, function(value, key) {
if (value.name == "Dashboard" && value.contentDashboard == true) {
//return value;
defer.resolve(value);
}
})
return defer.promise;
}
var availableDashboard = function() {
return $rootScope.getDashboardDetails.dashboardList[0];
}
var defaultDash = function(value) {
method goes here
}
if (dashboardCheck()) {
defaultDash(dashboardCheck());
} else {
defaultDash(availableDashboard())
}
})

You seem to make everything way more complicated than it should be, you want to find a certain dashboard and if not find just return the first.
There is no async code after making the request and you don't do anything with the request so I'm not sure why you're making it in the first place.
The much simpler version of what you're trying to do would be this:
return $http({
method: 'GET',
url: $rootScope.baseUrl + 'test/test/test/test',
headers: {
"token": token
}
})
.then(function(responce) {
var scopeDashboard = $rootScope.getDashboardDetails.dashboardList;
var dashboard =
//not sure why need to return a defer here, no async code provided
scopeDashboard.filter(
dashboard=>
dashboard.name == "Dashboard" && dashboard.contentDashboard == true
)[0] || scopeDashboard[0];
// if scopeDashboard is an object try this
// scopeDashboard[
// Object.keys(scopeDashboard)
// .filter(
// key=>
// scopeDashboard[key].name == "Dashboard" &&
// scopeDashboard[key].contentDashboard == true
// )[0] || 0
// ];
return [dashboard,response];
})
.then(
([dashboard,response])=>{
//you now have the response and dashboard, what would you like to do with it?
}
)
.catch(
err=>console.error("something went wrong",err)
)

Related

Dexie JS Nested Collections Not Resolving in Promise Chain

I've been working with Dexie JS to manage an IndexDB data store and now want to sync the data store with a remote database. The issue that I am having is that I want to nest all relational/child records under their respective parent records in a collection and then send the whole group/list off to the remote server, using some AJAX.
In practice, what I am seeing is that the child records are not present at the time they are pushed to the remote server. However, I do see them in console.log(). I know that this is because console.log() gets the actual data at a much later time than when the data is pushed remotely. I also know that this is a common issue with promise chains, but am somehow unable to solve it.
Here's what I have so far.
function PushRemote(items, modelName) {
console.log(items);
$.ajax({
type: 'POST',
url: '/' + modelName + '/AsyncSave',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
data: JSON.stringify(DataTransferItemList),
success: function (response) {
iDB.open().then(function () {
return iDB.table(modelName).update(response, { isNotSynced: "false" });
});
},
error: function (response) {
console.error(response);
}
});
}
function PushTableToRemote(modelName) {
iDB.transaction('rw',
iDB.Comments,
iDB.CommentRatings,
iDB.Posts,
iDB.PostRatings,
iDB.Reviews,
() => {
iDB.table(modelName).where({ isNotSynced: "true" }).toArray(items => {
items.forEach(item => {
if (modelName == 'Comments') {
iDB.CommentRatings.where({ CommentId: item.CommentId }).toArray(c => item.CommentRatings = c);
}
if (modelName == 'Posts') {
iDB.PostRatings.where({ PostId: item.PostId }).toArray(p => item.PostRatings = p);
}
})
return items;
})
.then(items => {
if (items && items.length > 0) {
PushRemote(item, modelName);
}
});
});
}
pushTableToRemote('Comments');
Alright, so it turns out if you can't go through the promise chain, then you go around the promise chain. ;)
Ended up implementing spawn() and yield with a generator function, instead of a promise chain (https://dexie.org/docs/Simplify-with-yield.html). This was way more straight forward, for me at least. And worked like a charm. Your mileage may vary.
function PushTableToRemote(modelName) {
spawn(function* () {
var items = yield iDB.table(modelName).where({ isNotSynced: "true" }).toArray();
if (items && items.length > 0) {
if (modelName == 'Comments') {
for (var i = 0; i < items.length; i++) {
var CommentRatings = yield iDB.CommentRatings.where({ CommentId: items[i].CommentId }).toArray();
items[i].CommentRatings = CommentRatings;
}
}
if (modelName == 'Posts') {
for (var i = 0; i < items.length; i++) {
var PostRatings = yield iDB.PostRatings.where({ PostId: items[i].PostId }).toArray();
items[i].PostRatings = PostRatings;
}
}
PushRemote(items, modelName);
}
});
}

How do I return array data from a foreach loop in Angular

I'm looping through a nested object to get a some data. This is working, but I can't seem to return the data and use it elsewhere.
I've tried putting the loop in a promise and couldn't get anywhere either. What am I doing wrong?
data: any = {
'1234': {
url: 'https://example1.com/',
path: 'uploads',
link: 'https://example1.com/uploads',
},
'5678': {
url: 'https://example2.com/',
path: 'uploads',
link: 'https://example2.com/uploads',
}
}
onSubmit(formData) {
this.formdata = formData;
Object.keys(this.data).forEach(key => {
if (key == this.formdata.pin) {
const url = this.data[key].url;
// have also tried this.url to no avail
}
});
// says undefined
console.log(url);
// set up headers, etc...
// I need to use here
this.http.post(url, body, head)
...
}
onSubmit(formData) {
this.formdata = formData;
let url; // Define here so that its accessible
Object.keys(this.data).forEach(key => {
if (key === this.formdata.pin) {
url = this.data[key].url;
// have also tried this.url to no avail
}
});
// Now url is in scope
console.log(url);
...
}
Switching your forEach to a map can simplify this; map return values, whereas forEach does not.
Old:
Object.keys(this.data).forEach(key => {
if (key == this.formdata.pin) {
const url = this.data[key].url;
}
});
// says undefined
console.log(url);
New: (I've also added a === in here based on the comment below)
const urls = Object.keys(this.data).map(key => {
if (key === this.formdata.pin) {
return this.data[key].url;
// have also tried this.url to no avail
}
});
console.log(urls);
map docs and forEach docs

Using angular using $interval or a promise to run code when api is completed?

I have an api call to that is a bit slow.
$http.jsonp(url, {
params: {
'client': 'translate_about',
'alpha': 1,
'hl': 'en'
}
})
.success(function (data) {
home.translate.list = data.sl;
//console.log(data.sl);
});
this next block can happen before or in parallel with home.translate.list is ready. Therefore, when home.translate.list is successful I need to another block of code to update.
home.modalOn = function (val) {
//open modal
home.isModal = true;
//this if block must wait for home.translate.list to be ready.
if(typeof home.translate.list !== 'undefined'){
home.activePageSelection = home.translate.list[val];
//call wikipedia and get data
home.callWiki(home.translate.list[val]);
}
};
$scope.$watch(function () {
return location.hash
}, function (value) {
var currentRouteParam = value.split('/').slice(-1)[0];
if (currentRouteParam !== '') {
home.modalOn(currentRouteParam);
} else {
home.modalOff()
}
});
Question:
How can I make sure home.translate.list is defined before executing the if block? Caveat, without putting it in the $http.success block.
Try like this
if (currentRouteParam !== '') {
var interval = $interval(function() {
if (typeof home.translate.list !== 'undefined') {
$interval.cancel(interval);
home.modalOn(currentRouteParam);
}
}, 500);
}
N:B:
you have to inject $interval service in your controller
I suggest you to put the watch in a function and call it in the success block
.success(function (data) {
home.translate.list = data.sl;
watchHash();
//console.log(data.sl);
});
function watchHash() {
$scope.$watch(function () {
...
}
You could save the promise for the translation instead of the list in your vm:
home.translatePromise = $http.jsonp(url, {
params: {
'client': 'translate_about',
'alpha': 1,
'hl': 'en'
}
})
.then(function (response) {
return response.data.sl;
});
Then reference the promise instead of the raw value:
home.modalOn = function (val) {
//open modal
home.isModal = true;
//this if block must wait for home.translate.list to be ready.
home.translatePromise.then(function(list){
home.activePageSelection = list[val];
//call wikipedia and get data
home.callWiki(list[val]);
});
};

Jquery Ajax prevent fail in a deferred sequential loop

So, I'm chaining together sequential ajax, to load an array of urls in order. Originally I used .then() instead of .always(), and either way it works fine - provided that all urls existed. However, since there's a possibility of missing files, I wanted to compensate for that, and then finally, inform the user of which files were missing, in order to make it easier to rectify.
However, the problem is, on a missing file/404, the code executes, like it should, but then exits the loop, preventing any further ajax calls. So I figure, I need some way of either handling the fail() and forcing a success regardless, or some other way of overriding the default behavior on a 404, so it continues progressing through the loop.
Unfortunately, the closest Google results, were how to do the opposite (force a failure on success).
var missing=[];
uLoadList.reduce(function(prev, cur, index) {
return prev.then(function(data) {
return $.ajax("/wiki/"+cur).always(function(data) {
var temp = $('#mw-content-text',data);
temp = $('pre',temp);
if(temp.length > 0)
{
//handle success
}else{
//handle failure
missing.push(cur);
}
});
});
}, $().promise()).done(function() {
if(missing.length > 0)
{
//notify of missing objects
}
//continue on.
});
One final note, to alleviate confusion: the URLs, and the script itself, are on a MediaWiki site - so even if a 404 is returned, there will always be page content, and will contain the element with the id of "mw-content-text".
Try
(function ($) {
$.when.all = whenAll;
function whenAll(arr) {
"use strict";
var deferred = new $.Deferred(),
args = !! arr
? $.isArray(arr)
? arr
: Array.prototype.slice.call(arguments)
.map(function (p) {
return p.hasOwnProperty("promise")
? p
: new $.Deferred()
.resolve(p, null, deferred.promise())
})
: [deferred.resolve(deferred.promise())],
promises = {
"success": [],
"error": []
}, doneCallback = function (res) {
promises[this.state() === "resolved"
|| res.textStatus === "success"
? "success"
: "error"].push(res);
return (promises.success.length
+ promises.error.length) === args.length
? deferred.resolve(promises)
: res
}, failCallback = function (res) {
// do `error` notification , processing stuff
// console.log(res.textStatus);
promises[this.state() === "rejected"
|| res.textStatus === "error"
? "error"
: "success"].push(res);
return (promises.success.length
+ promises.error.length) === args.length
? deferred.resolve(promises)
: res
};
$.map(args, function (promise, index) {
return $.when(promise).always(function (data, textStatus, jqxhr) {
return (textStatus === "success")
? doneCallback.call(jqxhr, {
data: data,
textStatus: textStatus
? textStatus
: jqxhr.state() === "resolved"
? "success"
: "error",
jqxhr: jqxhr
})
: failCallback.call(data, {
data: data,
textStatus: textStatus,
jqxhr: jqxhr
})
})
});
return deferred.promise()
};
}(jQuery));
// returns `Object {
// success: Array[/* success responses*/],
// error: Array[/* error responses */]
// }`
// e.g.,
var request = function (url, data) {
return $.post(url, {
json: JSON.stringify(data)
})
}, settings = [
["/echo/json/", "success1"], // `success`
["/echo/jsons/", "error1"], // `error`
["/echo/json/", "success2"], // `success`
["/echo/jsons/", "error2"], // `error`
["/echo/json/", "success3"] // `success`
];
$.when.all(
$.map(settings, function (p) {
return request.apply($, p)
})
)
.then(function (data) {
console.log(data);
// filter , process responses
$.each(data, function(key, value) {
if (key === "success") {
results.append(
"\r\n" + key + ":\r\n" + JSON.stringify(value, null, 4)
)
} else {
results.append(
"\r\n" + key + ":\r\n"
+ JSON.stringify(
value.map(function(v, k) {
v.data.responseText = $(v.data.responseText)
.filter("title, #summary, #explanation")
.text().replace(/\s+/g, " ");
return v
})
, null, 4)
)
}
})
}, function (e) {
console.log("error", e)
});
jsfiddle http://jsfiddle.net/guest271314/620p8q8h/

AngularJS promise is caching

I think I'm writing my promise incorrectly and I couldn't figure out why it is caching data. What happens is that let's say I'm logged in as scott. When application starts, it will connect to an endpoint to grab listing of device names and device mapping. It works fine at this moment.
When I logout and I don't refresh the browser and I log in as a different user, the device names that scott retrieved on the same browser tab, it is seen by the newly logged in user. However, I can see from my Chrome's network tab that the endpoint got called and it received the correct listing of device names.
So I thought of adding destroyDeviceListing function in my factory hoping I'll be able to clear the values. This function gets called during logout. However, it didn't help. Below is my factory
app.factory('DeviceFactory', ['$q','User', 'DeviceAPI', function($q, User, DeviceAPI) {
var deferredLoad = $q.defer();
var isLoaded = deferredLoad.promise;
var _deviceCollection = { deviceIds : undefined };
isLoaded.then(function(data) {
_deviceCollection.deviceIds = data;
return _deviceCollection;
});
return {
destroyDeviceListing : function() {
_deviceCollection.deviceIds = undefined;
deferredLoad.resolve(_deviceCollection.deviceIds);
},
getDeviceIdListing : function() {
return isLoaded;
},
getDeviceIdMapping : function(deviceIdsEndpoint) {
var deferred = $q.defer();
var userData = User.getUserData();
// REST endpoint call using Restangular library
RestAPI.setBaseUrl(deviceIdsEndpoint);
RestAPI.setDefaultRequestParams( { userresourceid : userData.resourceId, tokenresourceid : userData.tokenResourceId, token: userData.bearerToken });
RestAPI.one('devices').customGET('', { 'token' : userData.bearerToken })
.then(function(res) {
_deviceCollection.deviceIds = _.chain(res)
.filter(function(data) {
return data.devPrefix != 'iphone'
})
.map(function(item) {
return {
devPrefix : item.devPrefix,
name : item.attributes[item.devPrefix + '.dyn.prop.name'].toUpperCase(),
}
})
.value();
deferredLoad.resolve(_deviceCollection.deviceIds);
var deviceIdMapping = _.chain(_deviceCollection.deviceIds)
.groupBy('deviceId')
.value();
deferred.resolve(deviceIdMapping);
});
return deferred.promise;
}
}
}])
and below is an extract from my controller, shortened and cleaned version
.controller('DeviceController', ['DeviceFactory'], function(DeviceFactory) {
var deviceIdMappingLoader = DeviceFactory.getDeviceIdMapping('http://10.5.1.7/v1');
deviceIdMappingLoader.then(function(res) {
$scope.deviceIdMapping = res;
var deviceIdListingLoader = DeviceFactory.getDeviceIdListing();
deviceIdListingLoader.then(function(data) {
$scope.deviceIDCollection = data;
})
})
})
Well, you've only got a single var deferredLoad per your whole application. As a promise does represent only one single asynchronous result, the deferred can also be resolved only once. You would need to create a new deferred for each request - although you shouldn't need to create a deferred at all, you can just use the promise that you already have.
If you don't want any caching, you should not have global deferredLoad, isLoaded and _deviceCollection variables in your module. Just do
app.factory('DeviceFactory', ['$q','User', 'DeviceAPI', function($q, User, DeviceAPI) {
function getDevices(deviceIdsEndpoint) {
var userData = User.getUserData();
// REST endpoint call using Restangular library
RestAPI.setBaseUrl(deviceIdsEndpoint);
RestAPI.setDefaultRequestParams( { userresourceid : userData.resourceId, tokenresourceid : userData.tokenResourceId, token: userData.bearerToken });
return RestAPI.one('devices').customGET('', { 'token' : userData.bearerToken })
.then(function(res) {
return _.chain(res)
.filter(function(data) {
return data.devPrefix != 'iphone'
})
.map(function(item) {
return {
devPrefix : item.devPrefix,
name : item.attributes[item.devPrefix + '.dyn.prop.name'].toUpperCase(),
};
})
.value();
});
}
return {
destroyDeviceListing : function() {
// no caching - nothing there to be destroyed
},
getDeviceIdListing : function(deviceIdsEndpoint) {
return getDevices(deviceIdsEndpoint)
.then(function(data) {
return { deviceIds: data };
});
},
getDeviceIdMapping : function(deviceIdsEndpoint) {
return this.getDeviceIdListing(deviceIdsEndpoint)
.then(function(deviceIds) {
return _.chain(deviceIds)
.groupBy('deviceId')
.value();
});
}
};
}])
Now, to add caching you'd just create a global promise variable and store the promise there once the request is created:
var deviceCollectionPromise = null;
…
return {
destroyDeviceListing : function() {
// if nothing is cached:
if (!deviceCollectionPromise) return;
// the collection that is stored (or still fetched!)
deviceCollectionPromise.then(function(collection) {
// …is invalidated. Notice that mutating the result of a promise
// is a bad idea in general, but might be necessary here:
collection.deviceIds = undefined;
});
// empty the cache:
deviceCollectionPromise = null;
},
getDeviceIdListing : function(deviceIdsEndpoint) {
if (!deviceCollectionPromise)
deviceCollectionPromise = getDevices(deviceIdsEndpoint)
.then(function(data) {
return { deviceIds: data };
});
return deviceCollectionPromise;
},
…
};

Categories