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);
Related
I am trying to verify if the user is inside that list that I capture by axios, the issue is that I have used the FILTER option but it always returns undefined or [], being that if the user exists in that array.
I can't think what else to do, because I validate if it is by console.log() the variable with which I ask and if it brings data.
created() {
this.getStagesDefault()
this.getSalesman()
this.getStagesAmountByUser()
},
methods: {
async getSalesman(){
const { data } = await axios.get('salesman')
this.employees = data.data
},
getStagesAmountByUser(){
console.log(this.user['id'])
var objectUser = this.employees.filter(elem => {
return elem.id === this.user['id']
})
console.log(objectUser)
},
Console
Vue data
The method getSalesman is asynchronous, meaning that getStagesAmountByUser will start executing before getSalesman finishes.
Two ways to fix the problem:
Await the getSalesman method, but you have to make the created method async as well. Change the code as follows:
async created() {
this.getStagesDefault()
await this.getSalesman()
this.getStagesAmountByUser()
}
Attach a .then to the getSalesman function, and start the next one inside the .then. Change the code as follows:
created() {
this.getStagesDefault()
this.getSalesman().then(() => this.getStagesAmountByUser())
}
getSalesman is an async method. At the time of the filter, the array being filtered is still empty.
this.getSalesman() // this runs later
this.getStagesAmountByUser() // this runs right away
Have the methods run sequentially by awaiting the async method:
await this.getSalesman()
this.getStagesAmountByUser()
You can avoid the inefficient clientside filtering if you pass the id to the backend and only select by that id.
Additionally, created only gets called once unless you destroy the component which is also inefficient, so watch when user.id changes then call your method again.
Plus don't forget you must wrap any async code in a try/catch else you will get uncaught errors when a user/salesman is not found etc, you can replace console.error then with something which tells the user the error.
{
data: () => ({
employee: {}
}),
watch: {
'user.id' (v) {
if (v) this.getEmployee()
}
},
created() {
this.getEmployee()
},
methods: {
getEmployee() {
if (typeof this.user.id === 'undefined') return
try {
const {
data
} = await axios.get(`salesman/${this.user.id}`)
this.employee = data.data
} catch (e) {
console.error(e)
}
}
}
}
In NodeJS, I have an object like,
var scope = { word: "init" };
Using Object.defineProperty as described in MDN I re-write the get() function to be like this,
Object.defineProperty(scope, 'word', {
get: function() {
return Math.random();
}
});
Which correctly returns a new random each time I scope.word in the console. However the function must also get data from a function with a callback. So it works pretty much like a setTimeout,
Object.defineProperty(scope, 'word', {
get: function() {
setTimeout(() => {
return Math.random();
}, 1000)
}
});
Now everytime I do scope.word I get,
undefined
Because the get() function is synchronous. This can be of course solved by returning a Promise,
Object.defineProperty(scope, 'word', {
get: function() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(Math.random());
}, 1000)
});
}
});
But then I would need to do scope.word.then(...) but the whole idea behind what we are building is that the developer only has to scope.word like if it was a plain easy-to-use variable. Like an Angular's $scope or a VUE.js 'data'.
How can I make the get() function return an actual value, not a Promise? Is it possible to workaround using async / await? How?
One of the solutions is to pass the callback function like this.
const scope = {}
Object.defineProperty(scope, 'word', {
value: (cb)=>{
setTimeout(() => {
cb(Math.random())
}, 1000)
}
});
scope.word(res=>console.log(res))
I have a provider that should allow me to return specific data from an API I need. I have this function that does it:
public getStoryCount(key: string, val: number) {
return this.client.getEntries({
'content_type': xxxxxxxxxxxxxx,
[key]: val,
}).then((entries:any) => {
return entries.total;
});
}
This is my first time really using promises, but I am trying to call this in a component to get the value. I want to be able to get the value entries.total which when I console.log gets output.
I am building out an array of data to use in my view like so:
this.homeScreen.push({
'count': Provider.getStoryCount('sys.id', xxxx)
});
When I console.log the Provider function I can see the value in the promise and it looks like this:
__zone_symbol__state : true
__zone_symbol__value : 13 // this is the value I need to get
How can I save that number 13 to my array homeScreen['count'] value?? Or what am I doing wrong??
You are currently returning the Promise and not the actual value. That means modifying your component code to this:
Provider.getStoryCount('sys.id', xxxx)
.then((entries:any) => {
this.homeScreen.push({
'count': entries.total
});
}
});
should work.
You could also make your Provider service get the value and store it as an Observable so components can then subscribe to the value.
Since promises are asynchronous, you are not actually returning entries.total like you might think.
You will probably need to provide your own callback function, or simply return the promise (generated by this.client.getEntries) and tack on a then to the result. It might look something like this:
public getStoryCount(key: string, val: number) {
return this.client.getEntries({
'content_type': xxxxxxxxxxxxxx,
[key]: val,
});
// The 'then' will be added later
}
// ...
// Get the promise from the provider, and invoke 'then' here.
var storyCountPromise = Provider.getStoryCount('sys.id', xxxx);
storyCountPromise.then((entries:any) => {
this.homeScreen.push({
'count': entries.total
});
});
It is an async operation. You need to pass a function in then:
Provider.getStoryCount('sys.id', xxxx)
.then((total) => {
this.homeScreen.push({
'count': total
});
});
Firstly, to map the result of the promise to another value, use map.
public getStoryCount(key: string, val: number) {
return this.client.getEntries({
'content_type': xxxxxxxxxxxxxx,
[key]: val,
}).map((entries:any) => {
return entries.total;
});
}
Then when calling the promise returning function use then to get the result
Provider.getStoryCount('sys.id', xxxx).then((total) => ...use total...);
I used to have the following code:
function makeCall(userInfo) {
api.postUser(userInfo).then(response => {
utils.redirect(response.url);
})
// other logic
return somethingElse;
}
And I was able to write a test that looked like this:
const successPromise = Promise.resolve({ url: 'successUrl' })
beforeEach(function() {
sinon.stub(api.postUser).returns(successPromise);
}
afterEach(function() {
api.postUser.restore();
}
it "calls API properly and redirects" do
makeCall({});
expect(api.postUser).calledWith(userInfo).toBe(true);
successPromise.then(() => {
expect(utils.redirect.calledWith('successUrl')).toBe(true);
done();
}
emd
And everything was green.
Now, I had to add another promise to make another external call, before doing the api postUser call, so my code looks like this:
function makeCall(names) {
fetchUserData(names).then(userData => {
return api.postUser(userData).then(response => {
utils.redirect(response.url);
})
})
// other logic
return somethingElse;
}
where fetchUseData is a chain of many promises, such like:
function fetchNames(names) {
// some name regions
return Promise.all(names);
}
function fetchUserData(names) {
fetchUsersByNames(names).then(users => {
// For now we just choose first user
{
id: users[0].id,
name: users[0].name,
}
});
}
And the tests I had fail. I am trying to understand how to change my tests to make sure that I am still testing that I do the final API call properly and the redirect is also done. I want to stub what fetchUserData(names), to prevent doing that HTTP call.
You're not using promises correctly. Your code doesn't have a single return statement, when it should have several (or it should be using arrow functions in such a way that you don't need them, which you're not doing).
Fix your code:
function makeCall(names) {
// v---- return
return fetchUserData(names).then(userData => {
// v---- return
return api.postUser(userData).then(response => {
utils.redirect(response.url);
})
})
}
function fetchUserData(names) {
// v---- return
return fetchUsersByNames(names).then(users => {
// For now we just choose first user
// v---- return
return {
id: users[0].id,
name: users[0].name,
}
});
}
Once you've done that, you can have your test wait for all of the operations to finish.
Test code:
makeCall(['name']).then(() =>
expect(api.postUser).calledWith(userInfo).toBe(true);
expect(utils.redirect.calledWith('successUrl')).toBe(true);
done();
});
You should add a return statement, otherwise you are not returning promises nowhere:
function fetchNames(names) {
// some name regions
return Promise.all(names);
}
function fetchUserData(names) {
return fetchUsersByNames(names).then(users => {
// For now we just choose first user
{
id: users[0].id,
name: users[0].name,
}
});
}
So when you are using Promise.all(), then you will have as result of the promise an array with all the value returned by all the promises.
So then this method should look like this when called:
fetchNames(names).then((arrayOfResolvedPromises) => {
// here you will have all your promised resolved and the array holds all the results
});
So inside your test you can move your done inside the block where all the promises will be resolved.
In addition, I strongly suggest you to use a library as chai-as-promised for testing promises.
It has a lot of nice methods for testing your promises.
https://github.com/domenic/chai-as-promised
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
});