Edit query string parameter on retry - javascript

I've got the following code structure:
$.ajax({
type:"GET",
url:"http://example.com",
data:{
final:false
},
retry: {
attempt: 1,
limit: 3,
delay: 2000
},
success((data) => {
console.log("yay!");
}),
error((error) => {
if (this.retry.attempt++ <= this.retry.limit) {
var self = this;
if (self.retry.attempt > self.retry.limit) {
self.data.final = true;
}
setTimeout(() => {$.ajax(self)}, this.retry.delay);
}
})
});
The problem is that, when the request is called the first time, the data parameter gets erased, and its values appended to the url as a query string. So data no longer exists. The object that gets passed into the retry call is:
{
type:"GET",
url:"http://example.com?final=false",
retry: {
attempt: 2,
limit: 3,
delay: 2000
},
success((data) => {
//...
}),
error((error) => {
//...
}
}
How do I edit the final parameter of the request for the last retry?

You could try changing URL to "http://example.com?final=false" or "http://example.com?final="+false if false is a variable

try defining your request outside the ajax call:
const request = {
type:"GET",
url:"http://example.com",
data:{
final:false
}
};
$.ajax({...request, ...{
retry: {
attempt: 1,
limit: 3,
delay: 2000
},
success((data) => {
console.log("yay!");
}),
error((error) => {
if (this.retry.attempt++ <= this.retry.limit) {
const retryRequest = {...request, ...this};
if (this.retry.attempt > this.retry.limit) {
retryRequest.data.final = true;
}
setTimeout(() => {$.ajax(retryRequest)}, this.retry.delay);
}
})
});
alternatively, if you're only dealing with the one flag that only needs to be set on the last attempt:
$.ajax({
type:"GET",
url:"http://example.com",
retry: {
attempt: 1,
limit: 3,
delay: 2000
},
success((data) => {
console.log("yay!");
}),
error((error) => {
if (this.retry.attempt++ <= this.retry.limit) {
var self = this;
if (self.retry.attempt > self.retry.limit) {
self.data = {final: true};
}
setTimeout(() => {$.ajax(self)}, this.retry.delay);
}
})
});
also, fat arrow functions don't have local context, so your error function doesn't need to use self, i.e. this will work:
error((error) => {
if (this.retry.attempt++ <= this.retry.limit) {
if (this.retry.attempt > this.retry.limit) {
this.data = {final: true};
}
setTimeout(() => {$.ajax(this)}, this.retry.delay);
}
})

Related

Cypress - how to send parameters to same function inside it's callback

I'm trying to implement fixtures in my cypress project to avoid repeatedly sending same requests.
Command "ReadFixture" returns data from fixture file:
Cypress.Commands.add("ReadFixture", (fixtureName, firstKey, secondKey = "") => {
let fixturePath = `cypress/fixtures/${fixtureName}.json`;
if (secondKey.length === 0) {
cy.readFile(fixturePath).then(fixture => {
let dataArray = [];
let fixtureKeys = Object.keys(fixture);
fixtureKeys.forEach(key => {
let data = fixture[key][firstKey];
dataArray.push(data);
});
return cy.wrap(dataArray);
});
}
else {
cy.readFile(fixturePath).then(fixture => {
let dataArray = fixture[secondKey][firstKey];
});
return cy.wrap(dataArray);
};
});
Data is in json structure:
{
"id_0": {
"id": "id_0",
"more_data": [
"string_0"
]
},
"id_1": {
"id": "id_1",
"more_data": [
"string_1",
"string_2"
]
}
}
For some tests, only "id" is required, such test example:
it("Level 1", () => {
cy.ReadFixture("fixture_name", "id").then(urlKeys => {
urlKeys.forEach(keyUrl => {
cy.request({
method: "GET",
url: `${reqUrl}/${keyUrl}`
}).then(response => {
expect(response.status).to.be.equal(200);
});
});
});
})
Everything works as expected, however, for other tests "more_data" of single "id" is required. My approach is to read fixture twice - first get array of "id", like in "Level 1" test, then get "more_data" for each "id" in array. Example:
it("Level 2", () => {
cy.ReadFixture("fixture_name", "id").then(urlKeys => {
urlKeys.forEach(keyUrl => {
cy.ReadFixture("fixture_name", "more_data", keyUrl).then(keyData => {
cy.request({
method: "GET",
url: `${reqUrl}/${keyUrl}/more_data`
}).then(response => {
expect(response.status).to.be.equal(200);
expect(response.body.more_data).to.be.eql(keyData);
});
});
});
});
});
Problem is, when
cy.ReadFixture("fixture_name", "more_data", keyUrl)
is called, keyUrl is not defined for it and command returns array of "more_data" from all "id" because of if statement. Also, keyUrl can't be passed to request. Is it possible to go around this issue or the method I'm using is completely wrong?
try changing your else block to wrap the values inside your then callback:
else {
cy.readFile(fixturePath).then(fixture => {
let dataArray = fixture[secondKey][firstKey];
return cy.wrap(dataArray);
});
};

ChartJS - Returning labels and data from server on different calls

I am using classes to build charts and the thing with this one is: I need to return from the server dynamic labels and data every often. With this code I have accomplished to print on the console results I want for each function separately - array of carNames and array of avgSpeeds, but drawing the chart itself by the function drawSpeedChart has been really painful. Could anyone give me a way to pursue in order to get these printed results to render the chart? Thanks!!!
Here is the function that would supposedly render the chart:
drawSpeedChart() {
this.labels;
this.avgData;
this.getAvgData()
.then(avgData => {
this.avgData = avgData
console.log(this.avgData)
this.getCarNames()
}).then(carNames => {
this.carNames = carNames
console.log(this.labels)
}).then(this.createChart(this.labels, this.avgData))
}
Both console.log()return undefined for this snippet.
Main functions to return labels and data are respectively getCarNames and getAvgDataand they at least print in console the right result. Problem is to build the chart after that
Here is the full code.
window.onload = () => { new AvgSpeedChart(); }
class AvgSpeedChart {
constructor() {
this.selectElements()
this.drawSpeedChart()
}
selectElements() {
this.speedChartElement = document.querySelector('#speedChart')
}
createChart(carNames, avgData) {
return new Chart(this.speedChartElement, {
type: 'bar',
data: {
labels: carNames,
datasets: [{
label: "Velocidade média",
data: avgData
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
title: {
display: true,
text: 'Velocidade média'
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
},
legend: {
display: false
}
}
})
}
drawSpeedChart() {
this.labels;
this.avgData;
this.getAvgData()
.then(avgData => {
console.log(this.avgData)
this.getCarNames()
return this.avgData = avgData
}).then(carNames => {
console.log(this.labels)
return this.labels = carNames
}).then(this.createChart(this.labels, this.avgData))
}
getCarNames() {
return axios({
method: 'get',
url: "xxxxxxxxxxx",
auth: {
username: 'xxxxx',
password: 'xxxxx'
}
}).then(response => {
this.carNames = response.data.map(car => car.name)
console.log(this.carNames)
return this.carNames
}).catch(error => {
console.log(error)
})
}
getAvgData() {
return axios({
method: 'get',
url: "xxxxxx",
auth: {
username: 'xxxxx',
password: 'xxxxx'
}
}).then(response => {
this.devicesId = response.data.map(device => device.id)
return this.devicesId
}).then(devicesId => {
this.getAllSpeed(devicesId.map(e => this.getAvgSpeed(e)))
}).catch(error => {
console.log(error)
})
}
getAllSpeed(arr) {
return axios.all(arr)
.then((avgSpeeds) => {
console.log(avgSpeeds)
return avgSpeeds
})
}
getAvgSpeed(deviceId) {
return axios({
method: 'get',
url: "xxxxxxx",
auth: {
username: 'xxxxx',
password: 'xxxxx'
},
params: {
from: '2018-10-09T00:00:00',
to: '2018-10-09T23:59:59',
deviceId: `${deviceId}`
}
}).then(response => {
this.allSpeeds = response.data.map(pos => pos.speed)
let sumSpeed = this.allSpeeds.reduce(this.sumSpeeds, 0)
let numSpeed = this.allSpeeds.length === 0 ? 1 : this.allSpeeds.length
let avgCalc = ((sumSpeed/numSpeed)*1.852)
return avgCalc
}).catch(error => {
console.log(error)
})
}
sumSpeeds(total, sum) {
return total + sum
}
}
The problem with your drawSpeedChart method is that the properties which you are using with this does not exist on your class. I've made them local variables. And you don't need the third then because you already have all the information to call createChart method.
drawSpeedChart() {
let avgData;
this.getAvgData()
.then((avgDataResponse) => {
console.log(avgDataResponse);
avgData = avgDataResponse;
return this.getCarNames();
}).then((carNames) => {
console.log(carNames)
this.createChart(carNames, avgData)
}).catch((err) => {
console.log('Error', err);
})
}
Checkout this fiddle to see working example

Cannot access data from component method

I tried components methods in vue js. My code like this.
const Thread = Vue.component('threadpage', function(resolve) {
$.get('templates/thread.html').done(function(template) {
resolve({
template: template,
data: function() {
return {
data: {
title: "Data Table",
count: this.GetData
}
};
},
methods: {
GetData: function() {
var data = {
username : "newshubid",
data : {
page : 0,
length : 10,
schedule : "desc"
}
};
var args = {"data" : JSON.stringify(data)};
var params = $.param(args);
var url = "http://example-url";
var result;
DoXhr(url, params, function(response){
result = JSON.parse(response).data;
console.log("load 1", result);
});
setTimeout(function () {
console.log("load 2", result);
return result;
}, 1000);
}
},
created: function(){
this.GetData();
}
});
});
});
But, when I trying to use {{ data.count }} in template. Not showing result what i want. Even I tried return result in GetData.
Whats my problem ? And how to access data from methods ? Please help me, i'm a beginner. Thanks
See the edited code and comments I added below.
You tried to return the result by using return in the function from setTimeout, which won't help you return value from GetData.
Instead, You can just set the value in the callback function of your ajax request.
const Thread = Vue.component('threadpage', function(resolve) {
$.get('templates/thread.html').done(function(template) {
resolve({
template: template,
data: function() {
return {
data: {
title: "Data Table",
// NOTE just set an init value to count, it will be refreshed when the function in "created" invoked.
count: /* this.GetData */ {}
}
};
},
methods: {
GetData: function() {
var data = {
username : "newshubid",
data : {
page : 0,
length : 10,
schedule : "desc"
}
};
var args = {"data" : JSON.stringify(data)};
var params = $.param(args);
var url = "http://example-url";
var result;
var vm = this;
DoXhr(url, params, function(response){
result = JSON.parse(response).data;
// NOTE set data.count to responsed result in callback function directly.
vm.data.count = result;
});
// NOTE I think you don't need code below anymore.
// setTimeout(function () {
// console.log("load 2", result);
// return result;
// }, 1000);
}
},
created: function(){
this.GetData();
}
});
});
});

Wrapping a ajax call

I have a code, that will make inside the select function an ajax request.
oSelect
.select(function (oEvent) {
return oEvent.getSource();
})
.select(function (oControl) {
let oItem = oControl.getSelectedItem();
let aKeys = oItem.getKey().split("/");
return {plant: aKeys[0], wc: aKeys[1]};
})
.select(function (oSelectedItem) {
let oModel = self.getModel("weightProtocolService");
let oPlantFilter = new Filter("Plant", sap.ui.model.FilterOperator.EQ, oSelectedItem.plant);
let oWcFilter = new Filter("WorkCenter", sap.ui.model.FilterOperator.EQ, oSelectedItem.wc);
oModel.read("/CostCenterCalendarSet", {
success: function (oData, oResponse) {
return Rx.Observable.from(oResponse.data.results);
},
error: function (oError) {
return Rx.Observable.throw(oError);
},
filters: [oPlantFilter, oWcFilter]
});
})
.subscribe(function (oKey) {
console.log(oKey);
},
function (err) {
jQuery.sap.log.fatal(err);
});
My problem here is, that it will subscribe first before the ajax response appears.
How can I solve the problem?
Assuming RxJS 5, replace the last select with a mergeMap and return a new observable:
.mergeMap(function (oSelectedItem) {
let oModel = self.getModel("weightProtocolService");
let oPlantFilter = new Filter("Plant", sap.ui.model.FilterOperator.EQ, oSelectedItem.plant);
let oWcFilter = new Filter("WorkCenter", sap.ui.model.FilterOperator.EQ, oSelectedItem.wc);
return new Observable(observer => {
oModel.read("/CostCenterCalendarSet", {
success: function (oData, oResponse) {
observer.next(oResponse.data.results);
},
error: function (oError) {
observer.error(oError);
},
filters: [oPlantFilter, oWcFilter]
});
});
})
If oModel.read returns a promise, then you can simply do the following:
....
return Observable.fromPromise(oModel.read("/CostCenterCalendarSet", {
filters: [oPlantFilter, oWcFilter]
})
);
If oModel.read does not return a promise, then you would need a custom observable:
....
return Observable.create(function(observer) {
oModel.read("/CostCenterCalendarSet", {
success: function (oData, oResponse) {
return observer.onNext(oResponse.data.results); // or just .next(..) in case you are using rxjs5+
},
error: function (oError) {
return observer.onError(oError); // or just .error(..) in case you are using rxjs5+
},
filters: [oPlantFilter, oWcFilter]
});
});

How to do a synchronous call with jaydata

I'm a bit confused about the asynchous call to the DataBase.
I just want to have a javasctipt adapter class for the calls to the web sql. But I'm not quite sure how to do this. Propably somebody have a good hint for me.
The function OfflneAppDBAdapter.prototype.IsDeviceConfigured() should return true or false depending if there are any items in the Table DeviceConfig.
function OfflneAppDBAdapter() {
self = this;
this.deviceIsConfigured = false;
this.Init = function () {
$data.Entity.extend("$de.offlineapp.DeviceConfig", {
Id: { type: "int", key: true, computed: true },
Name: { type: "string", required: true },
Token: { type: "string" },
Type: { type: "string" }
});
$data.EntityContext.extend("$de.offlineapp.DataContext", {
DeviceConfig: { type: $data.EntitySet, elementType: $de.offlineapp.DeviceConfig }
});
}
self.Init();
$de.offlineapp.context = new $de.offlineapp.DataContext({
name: "webSql", databaseName: "OfflineApp"
});
$de.offlineapp.context.onReady(function () {
});
}
// ************************************************************************
// PUBLIC METHODS -- ANYONE MAY READ/WRITE
// ************************************************************************
OfflneAppDBAdapter.prototype.AddDeviceConfig = function (deviceName, deviceToken, deviceTyp) {
$de.offlineapp.context.onReady(function () {
var promise = $de.offlineapp.context.DeviceConfig.toArray(function (x) {
if (x.length == 0) {
var emp = new $de.offlineapp.DeviceConfig({ Name: deviceName, Token: deviceToken, Type: deviceTyp });
$de.offlineapp.context.DeviceConfig.add(emp);
$de.offlineapp.context.saveChanges();
}
}
)
});
}
OfflneAppDBAdapter.prototype.IsDeviceConfigured = function () {
$de.offlineapp.context.onReady(function () {
var promise = $de.offlineapp.context.DeviceConfig.toArray(function (x) {
if (x.length == 0) {
this.deviceIsConfigured = true;
}
}
)
});
return this.deviceIsConfigured;
}
var myOfflineAppDBAdapter = new OfflneAppDBAdapter();
myOfflineAppDBAdapter.AddDeviceConfig("DeviceName", "Token", "iPad");
console.log(myOfflineAppDBAdapter.IsDeviceConfigured());
As expected the console prints "false". I' aware that the jaydata call works with callbacks and the callbacks are not part of the main class. But there must be a possibility to do so?
I would really apprechiate any help.
Thank you in advance....
Chris
UPDATE:
As you requested the startup code:
function OfflineApplication()
{
self = this;
}
OfflineApplication.prototype.StartApplication = function () {
//Check if online, then sync and
if (navigator && navigator.onLine === true) {
this.IsDeviceConfigured();
}
else {
}
}
///check if the device has a base configuration
OfflineApplication.prototype.IsDeviceConfigured = function () {
myOfflineAppDBAdapter.GetDeviceConfiguration(function (result) {
if (result.length > 0) {
myOfflineAppDBAdapter.deviceIsConfigured = true;
myOfflineApplication.HasDeviceAnApplication();
}
else {
///Get the device base conf from the server.
myOfflineAppSynchronisationAdapter.getDeviceConfigurationByToken(token, myOfflineApplication.HasDeviceAnApplication);
myOfflineAppDBAdapter.deviceIsConfigured = true;
}
});
}
///check if the device has an "application config" in general
OfflineApplication.prototype.HasDeviceAnApplication = function () {
myOfflineAppDBAdapter.GetDeviceAnApplication(function (result) {
if (result.length > 0) {
myOfflineApplication.IsDeviceApplicationVersionLatest(result);
}
else {
myOfflineApplication.GetApplication(false);
}
});
}
///the application config could differ from time to time, so we have to check if a different application should be synct with the device
OfflineApplication.prototype.IsDeviceApplicationVersionLatest = function (result) {
myOfflineAppDBAdapter.DeleteDeviceAnApplication(function () { });
console.log(result);
}
///get the application from the server
OfflineApplication.prototype.GetApplication = function (clearConfig) {
if (clearConfig === true)
{
}
myOfflineAppSynchronisationAdapter.getDeviceApplicationByToken(token, myOfflineApplication.LoadApplication);
}
OfflineApplication.prototype.LoadApplication = function () {
console.log('Now everything is finde and the application gets loaded..');
}
var myOfflineAppDBAdapter = new OfflneAppDBAdapter();
var myOfflineAppSynchronisationAdapter = new OfflineAppSynchronisationAdapter();
var myOfflineApplication = new OfflineApplication();
myOfflineApplication.StartApplication();
There is no sync way. You handling promises wrong. Make your code simple :) You'll need something like this:
$data.Entity.extend("$de.offlineapp.DeviceConfig", {
Id: { type: "int", key: true, computed: true },
Name: { type: "string", required: true },
Token: { type: "string" },
Type: { type: "string" }
});
$data.EntityContext.extend("$de.offlineapp.DataContext", {
DeviceConfig: { type: $data.EntitySet, elementType: $de.offlineapp.DeviceConfig }
});
var context = new $de.offlineapp.DataContext({
name: "webSql", databaseName: "OfflineApp"
});
function AddDeviceConfig(deviceName, deviceToken, deviceTyp) {
return context.DeviceConfig.toArray()
.then(function (x) {
if (x.length == 0) {
var emp = new $de.offlineapp.DeviceConfig({ Name: deviceName, Token: deviceToken, Type: deviceTyp });
context.DeviceConfig.add(emp);
return context.saveChanges();
}
})
}
function IsDeviceConfigured() {
return context.DeviceConfig.toArray()
.then(function (x) {
return x.length > 0;
})
}
context.onReady()
.then(IsDeviceConfigured)
.then(console.log)
.then(function() { return AddDeviceConfig("DeviceName", "Token", "iPad"); })
.then(IsDeviceConfigured)
.then(console.log);
here's a fiddle which does this: http://jsfiddle.net/JayData/cpT5q/1/

Categories