AngularJS passing more than one value to promise's success callback - javascript

I have the following service method:
ResourcesService.prototype.list = function ()
{
var deferred = q.defer();
var settings = fetchSettings();
restService.getAll(resourceName, settings)
.then(function (response) {
deferred.resolve(response.data, {
count: response.headers('cr_count'),
total: response.headers('cr_total'),
last: response.headers('cr_last')
});
}, function (error) {
deferred.reject(error.statusText);
});
return deferred.promise;
}
As you can see I am passing two values to deferred.resolve, which are response.data and a metadata object.
Up in the call stack I have:
//"scenes" is an object that inherits from ResourcesService
scenes
.withLanguage('en-us')
.sort('creation')
.size(2)
.list()
.then(function (firstPage, metadata) {
//firstPage is the "response.data" from previous method
//metadata is undefined, but should be an object with all the values from the headers
});
Why is metadata undefined? I debugged ResourcesService and the headers are being read just fine, but the object passed is as argument to deferred.resolve is not being delegated to my callback function.
Does deferred.resolve support only one argument to be passed to the callback? Do I have to put this metadata in the same object along with the response?

You can't pass more then one parameter into then callback, only the one is expected and considered. What you can do however is to resolve your promise with an object. For example:
ResourcesService.prototype.list = function () {
var settings = fetchSettings();
return restService.getAll(resourceName, settings).then(function (response) {
return {
data: response.data,
metadata: {
count: response.headers('cr_count'),
total: response.headers('cr_total'),
last: response.headers('cr_last')
}
};
}, function (error) {
throw new Error(error.statusText);
});
}
Note, that I also fixed deferred anti-pattern in your code, you don't need dummy deferred object, because you already have promise you can return.
Then you would use it like this:
scenes
.withLanguage('en-us')
.sort('creation')
.size(2)
.list()
.then(function (response) {
var firstPage = response.data,
metadata = response.metadata;
});

While #dsfq is right about not resolving with more than one argument, if you're using q, you could also wrap your resolved values in an array and use .spread() instead of .then() to split them across arguments.
Created:
.then(function (response) {
// Resolve with a single array
deferred.resolve([response.data, {
count: response.headers('cr_count'),
total: response.headers('cr_total'),
last: response.headers('cr_last')
}]);
}
Consumed:
scenes
.withLanguage('en-us')
.sort('creation')
.size(2)
.list()
// .spread() instead of .then()
.spread(function (firstPage, metadata) {
// Works as expected
});

Related

Out of two functions, one can access variable's value, the other can't

The data fetched from API contains two types of elements: cycles which are objects of few attributes, and last_update object, which contains ID and last_update: <date>.
I'm processing them in the following way:
<script>
export default {
data: () => ({
jsonData: [],
lastUpdate: '',
items: [
{
action: 'mdi-calendar',
active: false,
items: [{ title: '' }],
title: 'Last Update',
}
]
}),
methods: {
async parseData() {
const response = await fetch("http://127.0.0.1:5012/api/v3/cycles", {
headers: {
"Content-Type": "application/json",
"mode": "cors"
}
})
let cycles = await response.json();
if (cycles.length < 1) {
console.error("Couldn't fetch cycles from DB...")
}
const last_update = cycles.filter(cycle => cycle.last_update);
this.lastUpdate = last_update[0].last_update;
console.log('Inside parseData:', this.lastUpdate);
cycles = cycles.map(({source, revision, elements}) => ({source, revision, elements}));
this.jsonData = cycles
},
setLastUpdate() {
this.items[0].items.title = this.lastUpdate;
console.log('Inside setLastUpdate:', this.items[0].items.title);
console.log('Inside setLastUpdate:', this.lastUpdate);
}
},
created() {
this.parseData();
this.setLastUpdate();
}
}
</script>
Now, the console shows the following outputs:
Inside setLastUpdate: <empty string>
Inside setLastUpdate: <empty string>
Inside parseData: 2023.01.24 08:44:32
The value gets read and updates the lastUpdate in data section. Why it can't be read by the setLastUpdate() function?
I was thinking that there may be something with async setting, so I made setLastUpdate() function async just for testing, but the asynchronous action in parseData() function only refers to fetching the data from API. After the data is there, all processing happens on the same set of data for both reading cycles and last_update.
How to refactor the code so that the lastUpdate is available for setLastUpdate() function and possibly update the data.items object?
As per looking at the console statement, function setLastUpdate is calling before the parseData function.
So, to make the lastUpdate property available for setLastUpdate, you need to call those functions asynchronously. You can create a single async function that will call both functions one by one and wait for the first to be executed. For example-
created() {
this.init();
},
methods: {
async init() {
await this.parseData();
await this.setLastUpdate();
},
},
One more thing-
instead of-
this.items[0].items.title = this.lastUpdate;
It should be-
this.items[0].items[0].title = this.lastUpdate;
Because inner items is having an array of objects structure.

Array is not array anymore when passed into Vuex action function

EDIT: Added extra code in the filterEvents snippet for more context.
I'm not quite understanding what's going on with my code. I'm trying to pass an array into an action function inside of my Vuex store. If I return a Promise inside of that action function, then the parameter being passed isn't of type Array and is instead an Object, which results in the reject() error that I have for the Promise.
Here's some code for context:
filterEvents({ commit }, events) {
console.log(Array.isArray(events)); //this ends up false
console.log(events);
return new Promise((resolve, reject) => {
if (!Array.isArray(events)) {
reject("Invalid argument: is not of type Array.");
}
let filtered = events.filter((event) => {
let now = new Date();
let event_stop = new Date(event.stop_time);
if (event_stop >= now || event_stop == null) {
return event;
}
});
resolve(filtered);
});
}
Here's where I call filterEvents; inside of getEvents;
getEvents({ state, commit, dispatch }, searchParams) {
.....
eventful.getEvents(searchParams).then(async (res) => {
.....
console.log(Array.isArray(res.data.events.event)); //this ends up true
console.log(res.data.events.event);
/* where I call it */
await dispatch("filterEvents", res.data.events.event).then((res) => {
.....
});
}).catch((err) => {
.....
});
}
Here's the output from the Chrome developer console. First two outputs are from getEvents and last two are from filterEvents
Would really like an explanation as to why this is the case. I'm going to bet it's something small, but it's 3 a.m. at the moment and my brain can't wrap around why it's not of type Array when passed into filterEvents.
I always try to check the length prop of the array which helps me out in such cases.
...
return new Promise((resolve, reject) => {
if (!Array.isArray(events) && !events.length) {
reject("Invalid argument: is not of type Array.");
}
.....
});
...
I finally understood what my issue was after taking another look at the object that was being logged on the console. I did not know that Vuex actions HAD to have two arguments if you want to pass in a payload into that function. For example, I initially did this
filterEvents(events) {
.....
}
but what I really needed to do was
filterEvents(context, events) {
.....
}
The context argument is the object that allows you to do things such as commit and dispatch. I usually destructure the context object (i.e. { commit, dispatch} ), so I for some reason never thought twice about it. You don't have to destructure the context object to use commit and dispatch; if you don't it would just be like
context.commit('function', payload);

axios promise returns correct value in "axios.all" function, but is undefined in the "then" function

I'm following a tutorial in which data from the git API is requested and a scoring algorithm will order that data.
The battle function will take an array of two elements, i.e two github users. We retrieve the profile and score for eah user from the getUserData method
module.exports = {
battle: function(players) {
return axios.all(players.map(getUserData))
.then(response => {
response.forEach( each=>console.log(each));
return response;
})
}
}
The getProfile and getRepos functions ork correctly in retrieving objects which have data on the users profile(username, followers, etc) and their repos(repo names, etc.). So I've omitted the code for both these functions as I already know they work for certain. Additionally, the calculateScore method also works and returns output as expected.
The console.log statement shows that the object with keys "profile" and "score" is correctly made, and prints out both the profile object data and the score as expected. So far so good.
function getUserData(player) {
axios.all([
getProfile(player),
getRepos(player)
])
.then(function(data) {
var profile = data[0];
var repos = data[1];
console.log({
profile: profile,
score: calculateScore(profile, repos)
})
return {
profile: profile,
score: calculateScore(profile, repos)
}
})
}
The Problem:
The callback function in "battle" should receive an array of size 2, with each element containing the profile and score for that particular player. e.g:
[
{
profile: {profile data for player1...}
score: 10 //or whatever the score is
},
{
profile: {profile data for player2...}
score: 2 //or whatever the score is
}
]
but instead the callback function is receiving [undefined, undefined] as its input from the axios.all function
Correct me if I'm wrong, but in promises, isn't the output from the "axios.all" method supposed to be the input for the "then" method. So why am I getting undefined if the console.log statement shows that axios.all is outputting the correct data?
Your getUserData function does not return anything. Change it as below:
function getUserData(player) {
return axios.all([
// ...
]);
}
That behaviour is because you return an array of undefined values when you do response.map where you replace all the items with undefined (console.log returns undefined).
Instead, return the actual result from the asynchronous call:
module.exports = {
battle: function(players) {
return axios.all(players.map(getUserData))
.then(response => {
response.forEach(each => console.log(each));
return response;
});
}
}

AngularJS - forEach() with empty array from $resource

I have an array that I get from a service but in the controller I get an empty value in the forEach() function. This is the code.
Controller
Here, both 'products' and 'copyProducts' are empty. I need to work with the 'copyProducts' array into the forEach() function.
app.controller("controllerApp", function($scope, serviceApp){
var products = serviceApp.query();
$scope.copyProducts = products;
angular.forEach($scope.copyProducts, function(value, key){
console.log("object: "+value);
})
});
Service
app.factory("serviceApp", function($resource){
return $resource("products.json", {}, {
getAll: {
method: "GET",
isArray: true
}
})
})
Your code is wrong since .query() is asynchronous so it doesn't finish immediately and the result is not ready on the next line synchronously. So it needs a callback function to trigger once it's done with it's work.
serviceApp.query().$promise.then(function(res) {
$scope.products = res;
$scope.copyProducts = res;
angular.forEach($scope.copyProducts, function(item) {
console.log(item)
})
});
Alternative:
serviceApp.query({}, function(res, headers){
//etc
});
By the way, if you want to use the getAll method you have defined in your resource then you would not be using query()
serviceApp.getAll().$promise.then(function(res){}).....etc

Method chaining in Javascript in Angular app

in my Angular web app, I have a module called ApplicationModule.
ApplicationModule has get and set functions.
In one of my controllers, I call function like below:
ApplicationModule.get().then(function (response) {
//do something with response
});
GET() function returns an object called application. With the returned object, I would like to do something with it. so I use then to chain the method but I get an error saying angular.js:13424 TypeError: Cannot read property 'then' of undefined.
Updated what get() is.
ApplicationModule.get = function () {
if (!localStorage.application) {
Restangular.all('session').one('user').get().then(function (response) {
application = {
"id": response.current_application_id,
"user_id": response.id,
"visa_type": response.current_type
}
localStorage.setItem("application", JSON.stringify(application));
return application
});
} else {
return JSON.parse(localStorage.application)
}
}
What am I doing wrong here?
Your method does neither return the application nor does it return an object at all if localStorage.application is falsy.
You are doing something asynchronous in your function, so you need to always return a promise:
ApplicationModule.get = function () {
if (!localStorage.application) {
return Rectangular.all('session').one('user').get().then(function (response) {
// ^^^^^^ returning the promise
var application = {
"id": response.current_application_id,
"user_id": response.id,
"visa_type": response.current_type
};
localStorage.setItem("application", JSON.stringify(application));
return application;
// ^^^^^^ returning the value that the promise will resolve with
});
} else {
return $q.resolve(JSON.parse(localStorage.application));
// ^^^^^^^^^^ creating a promise here as well for consistent interface
}
}
The method .then() is only applicable to promises. In order to do something with your application object I suggest storing the value as a variable.
var application = ApplicationModule.get();
foo(application);

Categories