Changes to a property in a service not triggering a change - angular-services

I have the following UserService where I'm simply having a property within the instance that determines whether the user isAuthenticated. For some reason, even though the value is being changes by setting this.isAuthenticated to various value, on logout and some other methods, that change is not being caught by Angular. I even tried doing a manual $digest and $apply but still no luck.
export default class UserService {
constructor($http, $state, AppConstants, JWTService) {
'ngInject';
this.$http = $http;
this.$state = $state;
this.isAuthenticated = false;
}
logout() {
this.isAuthenticated = false;
this.$state.go('login', null, { reload: true });
}
verify() {
return new Promise((resolve) => {
// if a user exists, then resolve
if (this.isAuthenticated) {
return resolve(true);
}
this.$http({
method: 'POST',
url: `${this.AppConstants.api}/verify`
})
.then(() => {
this.isAuthenticated = true;
return resolve(true);
})
.catch(() => {
this.isAuthenticated = false;
return resolve(false);
});
});
}
}
The code works and when I login the first time, I set the this.isAuthenticated to true and the this.verify works by posting. However, when I logout, even though the this.isAuthenticatd is so to false, the condition if (this.isAuthenticated) is still true when this.verify is called again.

"this" is not what you think it is inside your .then() function. The scope has changed. This scenario is common and just something you have to constantly keep in mind when working in javascript. Here is just one example of what you can do about it:
javascript promises and "this"

Related

Dangling transaction even though commit is called (node,Sequelize v5.21.9)

I have a route that uses the below shown method, in a node app that uses express.
I create a transaction but don't use it in the update method. Sequelize is configured to not use managed transactions and auto commit is set to false.
When this route is called multiple times/under load around 7 - 10 times per second (the number of calls differ), I end up with 5 or so dangling transactions even though commit is called for the transaction at the end of the method call.
(Because of these dangling transactions, subsequent calls and my node app is not able to make anymore db calls)
But if I pass the transaction in the params object this behaviour doesnt occur. And I don't get dangling transactions.
What can be the reason this is happening?
updateItem = (obj) => {
this.logDebug(`Updating item - `, obj);
return new Promise(async (resolve, reject) => {
let transaction;
try {
transaction = await this.getTransaction();
} catch(error) { return reject(error);
const params = {
where: {
id: obj.id
},
returning: true,
plain: true
};
return models[modelName].
update(obj, params).then(result => {
if (!result) { return result; }
result = JSON.parse(JSON.stringify(result[1]));
return result;
}).
then(async (result) => {
await transaction.commit();
return resolve(result);
}).
catch(async error => {
this.logError(`Failed to update - `, error);
await transaction.rollback();
return reject(error);
});
});
};
Causes dangling transaction.
const params = {
where: {
id: obj.id
},
returning: true,
plain: true
};
No dangling transactions occur.
const params = {
where: {
id: obj.id
},
returning: true,
plain: true,
transaction
};
Using Nodejs 12.4, Sequelize 5.21.9, postgres 9.x
Just using the created transaction in the params object somehow doesn't cause dangling transaction.
While NOT using the transaction in the params causes this issue of dangling transactions.
Wanted to know the cause of this behaviour? Is it a bug with my code? Or bug with Sequelize?
There are a few points here;
1- there is a missing curly bracket at the end of the first line (a simple type i guess)
2- you dont't have to use the returns other than the first one
3- it is not required to use double "then" fucntion at the end
4- if json parse fails your transaction probably leak
With all these the code may be updated like this;
updateItem = (obj) => {
this.logDebug(`Updating item - `, obj);
return new Promise(async (resolve, reject) => {
let transaction;
try {
transaction = await this.getTransaction();
} catch (error) { return reject(error); }
const params = {
where: {
id: obj.id
},
returning: true,
plain: true
};
return models[modelName].
update(obj, params).then(async (result) => {
try {
resolve(JSON.parse(JSON.stringify(result[1])));
await transaction.commit();
} catch (err) {
reject(err);
}
}).catch(async error => {
this.logError(`Failed to update - `, error);
await transaction.rollback(); // an extra try catch wont hurt here...
reject(error);
});
});
};
But things may get complicated if you want to make multiple updates in the future. I may suggest you to check Multiple Transaction Manager's Sequelize context for a clean way to arrange your transactions.

How can I am make sure these chain of functions in Node.js are performed in order (using promises)?

I have a set of functions in Node.js that I would like to load in a certain order. I will provide some mockup code abstracted and simplified:
function updateMyApp() {
loadDataToServer()
.then(() => useData())
.then(() => saveData())
.then(() => { console.log("updateMyApp done") })
}
function loadDataToServer() {
return new Promise( (resolve, reject) {
...preparing data and save file to cloud...
resolve()})
}
function handleDataItem(item) {
// Function that fetches data item from database and updates each data item
console.log("Name", item.name)
}
function saveData() {
// Saves the altered data to some place
}
useData is a bit more complex. In it I would like to, in order:
console.log('Starting alterData()')
Load data, as json, from the cloud data source
Iterate through every item in the json file and do handleDataItem(item) on it.
When #2 is done -> console.log('alterData() done')
Return a resolved promise back to updateMyApp
Go on with saveData() with all data altered.
I want the logs to show:
Starting useData()
Name: Adam
Name: Ben
Name: Casey
useData() done
my take on this is the following:
function useData() {
console.log('Starting useData()')
return new Promise( function(resolve, reject) {
readFromCloudFileserver()
.then(jsonListFromCloud) => {
jsonListFromCloud.forEach((item) => {
handleDataItem(item)
}
})
.then(() => {
resolve() // I put resolve here because it is not until everything is finished above that this function is finished
console.log('useData() done')
}).catch((error) => { console.error(error.message) })
})
}
which seems to work but, as far as I understand this is not how one is supposed to do it. Also, this seems to do the handleDataItem outside of this chain so the logs look like this:
Starting useData()
useData() done
Name: Adam
Name: Ben
Name: Casey
In other words. It doesn't seem like the handleDataItem() calls are finished when the chain has moved on to the next step (.then()). In other words, I can not be sure all items have been updated when it goes on to the saveData() function?
If this is not a good way to handle it, then how should these functions be written? How do I chain the functions properly to make sure everything is done in the right order (as well as making the log events appear in order)?
Edit: As per request, this is handleDataItem less abstracted.
function handleDataItem(data) {
return new Promise( async function (resolve) {
data['member'] = true
if (data['twitter']) {
const cleanedUsername = twitterApi.cleanUsername(data['twitter']).toLowerCase()
if (!data['twitter_numeric']) {
var twitterId = await twitterApi.getTwitterIdFromUsername(cleanedUsername)
if (twitterId) {
data['twitter_numeric'] = twitterId
}
}
if (data['twitter_numeric']) {
if (data['twitter_protected'] != undefined) {
var twitterInfo = await twitterApi.getTwitterGeneralInfoToDb(data['twitter_numeric'])
data['twitter_description'] = twitterInfo.description
data['twitter_protected'] = twitterInfo.protected
data['twitter_profile_pic'] = twitterInfo.profile_image_url.replace("_normal", '_bigger')
data['twitter_status'] = 2
console.log("Tweeter: ", data)
}
} else {
data['twitter_status'] = 1
}
}
resolve(data)
}).then( (data) => {
db.collection('people').doc(data.marker).set(data)
db.collection('people').doc(data.marker).collection('positions').doc(data['report_at']).set(
{
"lat":data['lat'],
"lon":data['lon'],
}
)
}).catch( (error) => { console.log(error) })
}
The twitterAPI functions called:
cleanUsername: function (givenUsername) {
return givenUsername.split('/').pop().replace('#', '').replace('#', '').split(" ").join("").split("?")[0].trim().toLowerCase()
},
getTwitterGeneralInfoToDb: async function (twitter_id) {
var endpointURL = "https://api.twitter.com/2/users/" + twitter_id
var params = {
"user.fields": "name,description,profile_image_url,protected"
}
// this is the HTTP header that adds bearer token authentication
return new Promise( (resolve,reject) => {
needle('get', endpointURL, params, {
headers: {
"User-Agent": "v2UserLookupJS",
"authorization": `Bearer ${TWITTER_TOKEN}`
}
}).then( (res) => {
console.log("result.body", res.body);
if (res.body['errors']) {
if (res.body['errors'][0]['title'] == undefined) {
reject("Twitter API returns undefined error for :'", cleanUsername, "'")
} else {
reject("Twitter API returns error:", res.body['errors'][0]['title'], res.body['errors'][0]['detail'])
}
} else {
resolve(res.body.data)
}
}).catch( (error) => { console.error(error.message) })
})
},
// Get unique id from Twitter user
// Twitter API
getTwitterIdFromUsername: async function (cleanUsername) {
const endpointURL = "https://api.twitter.com/2/users/by?usernames="
const params = {
usernames: cleanUsername, // Edit usernames to look up
}
// this is the HTTP header that adds bearer token authentication
const res = await needle('get', endpointURL, params, {
headers: {
"User-Agent": "v2UserLookupJS",
"authorization": `Bearer ${TWITTER_TOKEN}`
}
})
if (res.body['errors']) {
if (res.body['errors'][0]) {
if (res.body['errors'][0]['title'] == undefined) {
console.error("Twitter API returns undefined error for :'", cleanUsername, "'")
} else {
console.error("Twitter API returns error:", res.body['errors'][0]['title'], res.body['errors'][0]['detail'])
}
} else {
console.error("Twitter API special error:", res.body)
}
} else {
if (res.body['data']) {
return res.body['data'][0].id
} else {
//console.log("??? Could not return ID, despite no error. See: ", res.body)
}
}
},
You have 3 options to deal with your main issue of async methods in a loop.
Instead of forEach, use map and return promises. Then use Promise.all on the returned promises to wait for them to all complete.
Use a for/of loop in combination with async/await.
Use a for await loop.
It sounds like there's a problem in the implementation of handleDataItem() and the promise that it returns. To help you with that, we need to see the code for that function.
You also need to clean up useData() so that it properly returns a promise that propagates both completion and errors.
And, if handleDataItem() returns a promise that is accurate, then you need to change how you do that in a loop here also.
Change from this:
function useData() {
console.log('Starting useData()')
return new Promise( function(resolve, reject) {
readFromCloudFileserver()
.then(jsonListFromCloud) => {
jsonListFromCloud.forEach((item) => {
handleDataItem(item)
}
})
.then(() => {
resolve() // I put resolve here because it is not until everything is finished above that this function is finished
console.log('useData() done')
}).catch((error) => { console.error(error.message) })
})
}
to this:
async function useData() {
try {
console.log('Starting useData()')
const jsonListFromCloud = await readFromCloudFileserver();
for (let item of jsonListFromCloud) {
await handleDataItem(item);
}
console.log('useData() done');
} catch (error) {
// log error and rethrow so caller gets the error
console.error(error.message)
throw error;
}
}
The structural changes here are:
Switch to use async/await to more easily handle the asynchronous items in a loop
Remove the promise anti-pattern that wraps new Promise() around an existing promise - no need for that AND you weren't capturing or propagating rejections from readFromCloudFileServer() which is a common mistake when using that anti-pattern.
rethrow the error inside your catch after logging the error so the error gets propagated back to the caller

Rxjs in nestjs - Observable subscription error

I am newbie trying out rxjs and nestjs. The use case that I am currently trying to accomplish is for educational purpose. So I wanted to read a json file (throw an observable error in case of the file being empty or cannot be read) using the "fs" module. Now I create an observable by reading the file asynchronously, set the observer in the subject and then subscribe to the subject in the controller. Here is my code in the service
#Injectable()
export class NewProviderService {
private serviceSubject: BehaviorSubject<HttpResponseModel[]>;
// this is the variable that should be exposed. make the subject as private
// this allows the service to be the sole propertier to modify the stream and
// not the controller or components
serviceSubject$: Observable<HttpResponseModel[]>;
private serviceErrorSubject: BehaviorSubject<any>;
serviceErrorSubject$: Observable<any>;
filePath: string;
httpResponseObjectArray: HttpResponseModel[];
constructor() {
this.serviceSubject = new BehaviorSubject<HttpResponseModel[]>([]);
this.serviceSubject$ = this.serviceSubject.asObservable();
this.serviceErrorSubject = new BehaviorSubject<any>(null);
this.serviceErrorSubject$ = this.serviceErrorSubject.asObservable();
this.filePath = path.resolve(__dirname, './../../shared/assets/httpTest.json');
}
readFileFromJson() {
return new Promise((resolve, reject) => {
fs.exists(this.filePath.toString(), exists => {
if (exists) {
fs.readFile(this.filePath.toString(), 'utf-8' , (err, data) => {
if (err) {
logger.info('error in reading file', err);
return reject('Error in reading the file' + err.message);
}
logger.info('file read without parsing fg', data.length);
if ((data.length !== 0) && !isNullOrUndefined(data) && data !== null) {
// this.httpResponseObjectArray = JSON.parse(data).HttpTestResponse;
// logger.info('array obj is:', this.httpResponseObjectArray);
logger.info('file read after parsing new', JSON.parse(data));
return resolve(JSON.parse(data).HttpTestResponse);
} else {
return reject(new FileExceptionHandler('no data in file'));
}
});
} else {
return reject(new FileExceptionHandler('file cannot be read at the moment'));
}
});
});
}
getData() {
from(this.readFileFromJson()).pipe(map(data => {
logger.info('data in obs', data);
this.httpResponseObjectArray = data as HttpResponseModel[];
return this.httpResponseObjectArray;
}), catchError(error => {
return Observable.throw(error);
}))
.subscribe(actualData => {
this.serviceSubject.next(actualData);
}, err => {
logger.info('err in sub', typeof err, err);
this.serviceErrorSubject.next(err);
});
}
Now this is the controller class
#Get('/getJsonData')
public async getJsonData(#Req() requestAnimationFrame,#Req() req, #Res() res) {
await this.newService.getData();
this.newService.serviceSubject$.subscribe(data => {
logger.info('data subscribed', data, _.isEmpty(data));
if (!isNullOrUndefined(data) && !_.isEmpty(data)) {
logger.info('coming in');
res.status(HttpStatus.OK).send(data);
res.end();
}
});
}
The problem I face is that I can get the file details for the first time and the subscription is getting called once > its working fine. On the subsequent requests
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:470:11)
at ServerResponse.header (C:\personal\Node\test-nest.js\prj-sample\node_modules\express\lib\response.js:767:10)
at Ser
and the endpoint /getJsonData results in an error. Could someone help me out. i believe the subscription is not getting properly after the first call, but not sure how to end that and how to resolve that
The problem is that you're subscribing to your serviceSubject in your controller. Every time a new value is emitted, it will try to send the response. This works the first time, but the second time it will tell you it can't send the same response again; the request has already been handled.
You can use the pipeable first() operator to complete the Observable after the first value:
#Get('/getJsonData')
public async getJsonData() {
await this.newService.getData();
return this.newService.serviceSubject$.pipe(first())
}
You want your Observable to be shared (hot), so that every subscriber always gets the same, latest value. That's exactly what a BehaviourSubject does. So you should not convert your Subject to an Observable when you expose it publicly because you will lose this desired behavior. Instead, you can just cast your Subject to Observable, so that internally it is still a subject but it will not expose the next() method to emit new values publicly:
private serviceSubject: BehaviorSubject<HttpResponseModel[]>;
get serviceSubject$(): Observable<HttpResponseModel[]> {
return this.serviceSubject;
}
I think trying to convert the cold observable (the one that I created) to a hot/warm observable might help to plugin to a single source and emit and complete its execution and maintain the last emitted data to any cloned values. So I make the cold observable to a warm observable using the publishLast(), refCount() operators, and I could achieve the single subscription and the execution completion of the observable. Here are the change I made to work.
This is the service class change I made
getData() {
return from(this.readFileFromJson()).pipe(map(data => {
logger.info('data in obs', data);
this.httpResponseObjectArray = data as HttpResponseModel[];
return this.httpResponseObjectArray;
}), publishLast(), refCount()
, catchError(error => {
return Observable.throw(error);
}));
// .subscribe(actualData => {
// this.serviceSubject.next(actualData);
// }, err => {
// logger.info('err in sub', typeof err, err);
// this.serviceErrorSubject.next(err);
// });
}
And this is the change I made in the controller
public async getJsonData(#Req() req, #Res() res) {
let jsonData: HttpResponseModel[];
await this.newService.getData().subscribe(data => {
logger.info('dddd', data);
res.send(data);
});
}
Any answers that allow the observables to be first subscribed to subjects and then subscribing that subject in the controller is also welcome.
I found a great post on hot vs cold observables and how to make an observable subscribe to a single source and convert a cold, to a hot/warm observable - https://blog.thoughtram.io/angular/2016/06/16/cold-vs-hot-observables.html
I would recommend to return the Promise directly to the controller. Here, you don't need an Observable. For the subscribers, you additionally emit the value of the Promise to your serviceSubject.
async getData() {
try {
const data = await this.readFileFromJson();
this.serviceSubject.next(data as HttpResponseModel[]);
return data;
} catch (error) {
// handle error
}
}
In your controller you can just return the Promise:
#Get('/getJsonData')
public async getJsonData() {
return this.newService.getData();
}

How can I extend OrderWizard.confirmation.module for kilimanjaro?

I need add a tracking script on the confirmation page and want to do it via NetSuite Recommended best practice , can some one share me how to extend this particular module? and add a tracking script.
I believe I will need to add a child view and add a template to it which contains the tracking script.
There is actually a lot of step on extending the OrderWizard.Module.Confirmation.
First is you need to extend the Wizard Module like this.
define('OrderWizard.Module', [
'Wizard.Module',
'yourtpl.tpl',
'jQuery'
], function OrderWizardModule(
WizardModule,
YourTPL,
jQuery
) {
'use strict';
return WizardModule.extend({
template: YourTPL,
initialize: function initialize(options) {
this.wizard = options.wizard;
},
submit: function submit() {
var self = this;
var promise;
promise = jQuery.Deferred();
if(true) {
return self.isValid();
}
return promise;
},
isValid: function isValid() {
var promise;
promise = jQuery.Deferred();
if(true) {
promise.resolve();
} else {
promise.reject();
}
return promise;
},
getContext: function getContext() {
return {
test: 'test'
};
}
});
});
And then after that you need to determine the step of the order wizard confirmation page then you can push there the view that I paste above.

How do I test this async method call in reactjs using mocha

// Balance.jsx
...
updateToken () {
const parseResponse = (response) => {
if (response.ok) {
return response.json()
} else {
throw new Error('Could not retrieve access token.')
}
}
const update = (data) => {
if (data.token) {
this.data.accessTokenData = data
} else {
throw new Error('Invalid response from token api')
}
}
if (this.props.balanceEndpoint !== null) {
return fetch(this.props.accessTokenEndpoint, {
method: 'get',
credentials: 'include'
})
.then(parseResponse)
.then(update)
.catch((err) => Promise.reject(err))
}
}
componentDidMount () {
this.updateToken()
.then(() => this.updateBalance())
}
}
// Test
it('updates the balance', () => {
subject = mount(<Balance {...props} />)
expect(fetchMock.called('balance.json')).to.be.true
})
I can't figure out how to test the above using Mocha. The code is does work the method updateBalance is called and the fetch api call actually does happen, but the test still fails. If I call updateBalance() synchronously it passes... How do I tell the test to wait for the promise to resolve?
You don't really say what you want to test that the
method does, but if all you want to test is that the method resolves on a network call, then there is no need for Sinon or any of that, as this is all you need:
describe("BalanceComponent", () => {
it("should resolve the promise on a successful network call", () => {
const component = new BalanceComponent({any: 'props', foo: 'bar'});
// assumes you call a network service that returns a
// successful response of course ...
return component.updateToken();
});
});
This will test that the method actually works, but it is slow and is not a true unit test, as it relies on the network being there and that you run the tests in a browser that can supply you with a working implementation of fetch. It will fail as soon as you run it in Node or if the service is down.
If you want to test that the method actually does something specific, then you would need to to that in a function passed to then in your test:
it("should change the token on a successful network call", () => {
const component = new BalanceComponent({any: 'props', foo: 'bar'});
const oldToken = component.data.accessTokenData;
return component.updateToken().then( ()=> {
assert(oldToken !== component.data.accessTokenData);
});
});
If you want to learn how to test code like this without being reliant on there being a functioning link to the networked service you are calling, you can check out the three different techniques described in this answer.

Resources