Reputation: 371
Currently I'm trying to make repeating Ajax-Calls dynamic so my code gets better manageable. While doing so I encountered that I sometimes need dynamic data-attributes and -values. There is always just one data-value that changes, the other parameters stay the same. Doing so I could easily chain promises.
So here is an example of what I use as template for those Ajax-Calls:
var prepareAjax = {
iterateValues: [1230,1280,4000,9000],
ajaxOptions: [{
timeout: 10000,
type: "GET",
data: `{
param1: item,
param2: obj.sessionId,
param3: 1
}`,
url: 'https://someurl.tld/target'
}],
sessionId: '<somestring>'
};
After this object I'm calling a function that should extract the ajaxOptions
from the object like so:
function fetchChain(obj)=>{
var ajaxPromises = [], tempObj;
obj.iterateValues.map((item, index)=> {
tempObj = obj.ajaxOptions[0];
tempObj.data = eval('('+tempObj.data+')');
ajaxPromises.push(new Promise(
(resolve, reject)=>{
namespace.fetchData(tempObj);
}
);
}
}
What I'm doing here is creating a promise and Ajax-Call for every ìterateValue
. Then I'm using eval (yes, evil) to resolve the variables of the current context (fetchChain
) and feed it to fetchData
. The functions are executed withhin a namespace, so I'm calling them with namespace.fetchChain(prepareAjax)
in example.
The problem
This example only works for one iteration since eval also seems to change obj
permanently, even if I only eval/modify the tempObj
, but obviously I want to reuse the template on every iteration. The only value that needs to be resolved is item
, the parameters stay the same.
I also know new Function()
is a better alternative to eval
but I couldn't get it to work neither. What is more weird for me is that the function worked previously when eval'ing the data-attributes directly inside the Ajax-Call without using a preparation function like for fetchChain()
. I'm stuck at this point, even after reading through several Answers on SO.
For completeness, here is the fetchData function:
function fetchData(obj)=>{
// single ajax-calls should use a delay of 0
obj.timeout = ((obj.timeout) ? obj.timeout : 10000),
obj.retries = ((obj.retries) ? obj.retries : 5),
obj.delay = ((obj.delay) ? obj.delay : 1000),
obj.type = ((obj.type) ? obj.type : "GET"),
obj.cnt = ((obj.cnt) ? obj.cnt++ : 0);
var sumDelay = obj.delay*(obj.cnt+1);
setTimeout(()=>{
return new Promise((resolve, reject)=>{
return $.ajax(obj)
.done((response)=>{
return resolve(response);
}).fail((error)=>{
if(obj.cnt++ >= obj.retries){
return resolve('Error');
}
fun.fetchData(obj);
}).always((xd)=>{
})
})
}, sumDelay)
}
A Solution
A solution I'm thinking of would be to prepare the object before feeding it to fetchChain()
. Or to be more clear: in the context where prepareAjax
gets created. Obviously I would prefer to directly handle this process inside fetchChain()
.
The Error
When executing fetchChain
like described above I'm getting the Error Unexpected Identifier
on second iteration inside of the eval. ([object Object]) When debugging one could see the obj
also changed its value for data
.
Upvotes: 0
Views: 75
Reputation: 371
While trying to get rid of eval()
I encountered the same problems as with eval → The original object always got changed too and this way all dynamic parameters were the same by the end of execution.
Turns out I had a fundamental misunderstanding on how '='-Operator works on objects. The objects doesn't get cloned and instead referenced. Since there are already better explained answers depending cloning objects in js, I'm just linking to one answer here.
So what actually happens when I use eval
on the tempObj
is that it not only turns the template-string (data-attribute) of tempObj
into an object, but also the template-string of obj
it references to.
An easy approach to fix this problem, which seems to be commonly used:
var A = JSON.parse(JSON.stringify(obj));
Others, that doesn't work for me, due to how they work:
var A = Object.create(obj);
var A = Object.assign({},obj);
There are even more and better solutions, but just have a look at the link above.
Since I was asking for a solution utilizing eval
, I'm giving an example which actually works and even supports multiple dynamic parameters and parameter-values.
function chainAjax(){
var ajaxPromises = [], tempObj,
iterateProps = Object.getOwnPropertyNames(obj.iterateValues[0])
obj.iterateValues[0][iterateProps].map((item, index)=> {
tempObj = JSON.parse(JSON.stringify(obj.ajaxOptions[0]));
tempObj.data = eval('('+tempObj.data+')');
ajaxPromises.push(new Promise(
(resolve, reject)=>{
fetchData(tempObj).then(...)
}
))
})
return Promise.all(ajaxPromises);
}
As template I would use something like this:
var prepareAjax = {
iterateValues: [{
appid: [1230,1280,4000,9000]
}],
ajaxOptions: [{
timeout: 10000,
type: "GET",
data: `{
appid: obj.iterateValues[0].appid[index],
sessionid: '<somestring>',
wizard_ajax: 1
}`,
url: 'https://someurl.tld/target'
}]
}
Last but not least an example on how to do this w/o eval:
function fetchChain(obj)=>{
var ajaxPromises = tempArray = [], tempObject,
iterateProps = Object.getOwnPropertyNames(obj.iterateValues[0]);
// Prepare Data-Objects and resolve dynamic vars
obj.iterateValues[0][iterateProps[0]].map((item, index)=> {
tempObject = JSON.parse(JSON.stringify(obj.ajaxOptions[0])); // clone trick
iterateProps.map(keyname =>
tempObject.data[keyname] = obj.iterateValues[0][keyname][index]
)
tempArray.push(tempObject);
});
tempArray.map((item, index)=> {;
ajaxPromises.push(
fun.fetchData(item).then((response)=>{
return response;
});
)
})
return Promise.all(ajaxPromises);
}
And a slightly different template:
var prepareAjax = {
iterateValues: [{ // matching property-names
appid: [1230,1280,4000,9000]//═══════╗
}], // ║
ajaxOptions: [{ // ║
data: { // ║
appid: '',//<════════════════════╝
sessionid: '<somestring>',
wizard_ajax: 1
},
url: 'https://somedomain.tld/target'
}]
}
Upvotes: 0
Reputation: 1964
Why would not you do something like to get that obj dynamically?
e.g.:
const iterateValues = [1230,1280,4000,9000];
const getAjaxOpts = value => {
return {
ajaxOptions: [{
timeout: 10000,
type: "GET",
data: /* apply data here from value */
url: 'https://someurl.tld/target'
}],
sessionId: '<somestring>'
};
};
and do iterations like:
const mapped = iterateValues.map(x => getAjaxOpts(x));
// do your promise things afterwards
Upvotes: 1