How can I stop multiple calls to a function inside an interval? - javascript

How could I ensure that one one call of a function in an interval can run at once?
My code looks like this:
var promisePutTestQuestion;
onEnter: ['$interval', 'questionService',
($interval, qus: IQuestionService) => {
promisePutTestQuestion = $interval(() => {
qus.putTestQuestionResponses();
}, 5 * 1000);
}],
onExit: ['$interval',
($interval) => {
$interval.cancel(promisePutTestQuestion);
}],
The problem I am having is if the putTestQuestionResponse (which returns a promise) is slow then I have multiple putTestQuestionResponse function calls one after the other.
Would appreciate if anyone has any ideas on this. Thanks

In javascript it's safe to use state variable for this purpose. Whenever qus.putTestQuestionResponses operation takes a lot of time next call will be skipped. So just maintain right state of processing variable, and if it's true quit the interval callback without running task again.
var promisePutTestQuestion;
let processing = false;
onEnter: ['$interval', 'questionService',
($interval, qus: IQuestionService) => {
promisePutTestQuestion = $interval(() => {
if (processing)
return;
processing = true;
qus.putTestQuestionResponses()
.then(() => processing = false)
}, 5 * 1000);
}],
onExit: ['$interval', ($interval) => {
$interval.cancel(promisePutTestQuestion);
}]
Not sure what promise framework you're using, but () => processing = false should be either in finally() for $q I guess, and in the then that should be after catch to ensure it is executed in any case.
So one of these:
qus.putTestQuestionResponses().finally(() => processing = false)
or
qus.putTestQuestionResponses().catch(...).then(() => processing = false)`
or even
qus.putTestQuestionResponses().then(() => processing = false).catch(() => processing = false)

Related

Angular Code Inside Subscribe() How to Run Synchronouly

_subscriptions.push(
// Update list on OG selection
ogViewModel.current$.subscribe(
(group) => {
this.groupUuid = group.ogUuid;
this.groupId = group.id;
this.getDevices();
this.getSubOgs();
this.updateGroup(group);
},
),
I have a block of code inside subscribe. However, it seems that they are not executed in order. this.getSubOgs() is executed before this.getDevices(). Both are HTTP calls that returns an observable. How do I make sure this.getDevices() is executed first?
Click to See Codes
this is basically #MishaMashina's suggestion
_subscriptions.push(ogViewModel.current$.subscribe(
(group) => {
this.groupUuid = group.ogUuid;
this.groupId = group.id;
this.getDevices();
this.updateGroup(group);
},
),
both getDevices() and getSubOgs() remain seperate methods
in
public getDevices() { this.myApiClient.getDevices().Subscribe(
let onlyThisTime: boolean = true;
(next) => doStuff
(error) => doErrorStuff
() => {if(onlyThisTime){this.getSubOgs();}}
)}

How can I update a global variable in Cypress tests?

I want to use a Global variable inside my Cypress test file but its value isn't changing as expected despite adding waits.
const builder = {
stepsArr: []
};
describe('my test', () => {
beforeEach(() => {
cy.intercept('/graphql', (req) => {
req.continue((res) => {
if (res.body.data?.steps) {
builder.stepsArr = res.body.data.steps.steps;
console.log({ stepsArr: builder.stepsArr }); // logs correctly!
}
});
}).as('graphqlRequest');
});
it.only('should check global var', () => {
cy.waitFor('steps');
cy.wrap({ getStepByTitle: pathwayBuilder.getStepByTitle })
.invoke('getStepByTitle',
'some title',
builder.stepsArr // always empty array!
)
.then((stepObj) => {
cy.log(stepObj);
});
});
});
The order of execution is correct but the value of Global variable isn't updating. Its showing empty array when I invoke my function despite retrying for like 100 times. What could be wrong?
cy.waitFor('steps'); is from a command in support/commands.js file
Cypress.Commands.add('waitFor', operationName => {
cy.wait('#graphqlRequest').then(({ request }) => {
if (request.body.operationName !== operationName) {
cy.log('Waiting for:', operationName)
return cy.waitFor(operationName)
}
return null
})
})
The function just logs the parameters on console
exports.pathwayBuilder = {
getStepByTitle: (title, steps) => {
console.log("Search", title);
console.log("Steps", steps); // empty!
}
}
I think the .invoke() args are wrong, see this example invoke() - functions with arguments
const fn = (a, b, c) => {
return a + b + c
}
cy.wrap({ sum: fn })
.invoke('sum', 2, 4, 6)
.should('be.gt', 10) // true
.and('be.lt', 20) // true
...the function takes three arguments and they are passed in comma-separated.
You getStepByTitle accepts two arguments, but you pass in one - an object containing the second argument
so
.invoke('getStepByTitle', {
steps: builder.stepsArr // always empty array!
})
should be
.invoke('getStepByTitle', 'aTitle', builder.stepsArr )
Some more things I found in running the test
getStepByTitle() needs to return something, otherwise stepObj (the result of the .invoke()) will be undefined
the cy.wait() does not succeed (for me) inside the custom command, but it does work if I inline the code of the custom command in the test (for ref Cypress 7.1.0)
the cy.wrap({ getStepByTitle }).invoke(... is evaluating before the cy.wait() finishes. Looks like some sort of optimization, Cypress is invoking when the command is added to the queue.
Substitute
.then(obj => obj.getStepByTitle('some title', builder.stepsArr))`
for
.invoke('getStepByTitle', 'some title', builder.stepsArr )

How to chain suscriptions

Hi I am trying to chain following subscriptions.
changeBranch() {
const bottomSheetRef: MatBottomSheetRef = this.bottomSheet.open(CCSBranchComponent, {
data: this.branches
});
this.subscription.add(bottomSheetRef.instance.change.subscribe((branch: Branch) => {
this.branchInfo = `Description : ${branch.author}\nAuthor : ${branch.id}\nCreated date :${branch.created}`;
this.blockpointBranchForm.get('branch').setValue(branch.id);
}));
this.subscription.add(bottomSheetRef.afterDismissed().subscribe(() => {
this.returnSelectedBranch.emit(this.blockpointBranchForm.get('branch').value);
}));
}
Here if bottomSheetRef.instance.change.subscribe is called before the sheet loads, it throws undefined. So i am trying the implement something that looks like this
this.subscription.add(this.bottomSheet.open(CCSBranchComponent, {
data: this.branches
}).instance.change.subscribe((branch: Branch) => {
this.branchInfo = `Description : ${branch.author}\nAuthor : ${branch.id}\nCreated date :${branch.created}`;
this.blockpointBranchForm.get('branch').setValue(branch.id);
}).afterDismissed().subscribe(() => {
this.returnSelectedBranch.emit(this.blockpointBranchForm.get('branch').value);
}));
Here the second subscribe is called on the subscription returns by first. How do I access the observable in the chain?
I guess what you want is to chain the the actions what are done when subscribing.
You can achieve this by
bottemsheetRef.instance.change.pipe(
switchmap(resultFromChange => bottomSheetRef.afterDismissed
).subsbribe(resultFromAfterDismissed => {// do whatever you like})

How to make sure the function is executed in VueJS

I'm trying to execute 3 functions, and after than console.log the values that they change. I think there should be better approach for this kind of problems, but I'm not sure what it is. What I've done is I went old school, and added loading flag. Basically, loading = 3, when function is loaded, loading--
I'd like to demonstrate my current code (well actually it's not the same, but it will work for demo purposes), so you can get the feeling:
data:() => ({
loading: 3,
first: null,
second: null,
third: null
}),
methods: {
first() {
this.$route.get('/data/for/first').then(response => {
this.first = response.data;
this.loading--;
})
},
second() {
this.$route.get('/data/for/second').then(response => {
this.second = response.data;
this.loading--;
})
},
third() {
this.$route.get('/data/for/third/a').then(responseA => {
let thirdA = responseA.data;
this.$route.get('/data/for/third/b').then(responseB => {
let thirdB = responseB.data;
if (thirdA === thirdB) {
this.third = true;
}
this.loading--;
})
})
},
fireFunctions() {
this.first();
this.second();
this.third();
}
},
watch: {
loading: function() {
if (this.loading === 0) {
console.log(this.first, this.second, this.third)
}
}
}
The output looks like this:
dataForFirst, dataForSecond, dataForThird;
But, if I don't use the watcher, and load this.fireFunctions() in mounted() i get:
dataForFirst, dataForSecond, undefined;
Now, as I understand, this is happening because this.third() needs more time to process the data. As you can see in the code, I added loading flag. So, fire functions will only execute when all of the functions are loaded.
I don't think this is the best approach, so I'd like to hear your opinion on this one.
How would you handle it?
Use Promise.all to wait on all your async functions to return and then run whatever code you need to afterward, example:
methods: {
async all() {
let [first, second, third] = await Promise.all([
this.$route.get('/data/for/first'),
this.$route.get('/data/for/second'),
this.$route.get('/data/for/third')
]);
this.first = first;
this.second = second;
this.third = third;
console.log(first, second, third);
}
}

Push items to array when variable is done being totaled

Using Angular, I have created a component that fires an initial web request along with two others used in loops. I am looping through these items to set siteCount which is declared in the first loop's request. I am using setTimeout() to push the totaled amount to an array, however this is probably not best practice.
constructor(public http: HttpClient, public chart: ChartDetails) {
this.http.get(`https://siteurl.com/items?$filter=Hub/Title eq 'Project Hub' and Active eq 'Yes'`).subscribe(data => {
data['value'].forEach(item => {
let siteCount: number = 0
this.http.get(`${item.Site}/lists/?$filter=BaseTemplate eq 171`).subscribe(data => {
data['value'].forEach(list => {
this.http.get(`${item.Site}/lists(guid'${list.Id}')/items`).subscribe(data => {
this.myTasks = data['value']
this.myTasks.forEach(variable => {
variable.internalListName = list.EntityTypeName.replace("List", "")
});
siteCount += data['value'].length
})
});
})
setTimeout(() => {
if (siteCount) {
this.chart.goalChartLabels.push(item.Title)
this.chart.goalChartData.push(siteCount)
}
}, 500);
});
})
}
Without using setTimeout, how can I push item.Title and siteCount when siteCount is done being totaled and before being reset to 0 for the next item?
My initial version would look like this:
import { zip } from 'rxjs';
...
this.http.get(`https://siteurl.com/items?$filter=Active eq 'Yes'`).subscribe(data => {
data['value'].forEach(item => {
this.http.get(`${item.Site}/lists/?$filter=BaseTemplate eq 171`).subscribe(data => {
const itemListObservables = data['value']
.map(list => this.http.get(`${item.Site}/lists(guid'${list.Id}')/items`));
zip(...itemListObservables)
.subscribe(itemLists => {
const totalCount = itemLists
.map(l => l.length)
.reduce((sum, val) => sum + val, 0)
if (totalCount > 0) {
this.chart.goalChartLabels.push(item.Title)
this.chart.goalChartData.push(totalCount)
}
itemLists.forEach(variable => {
variable.internalListName = list.EntityTypeName.replace("List", "");
this.myTasks.push(variable);
});
});
})
});
})
zip function waits for all observables to emit a value and returns an array of them.
As for best practices:
HttpService calls should be done at least in ngOnInit. You will probably use inputs for running these requests and they won't be defined in constructor.
Making three-level nested loops with HTTP requests on each level is not pretty. Consider making this a single resource on server side.
You should use forkJoin() to make all the requests and then get the response.
The rxjs docs link.

Categories

Resources