Pandora
Pandora

Reputation: 371

Prepare variables of context B in context A and resolve them inside context B

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

Answers (2)

Pandora
Pandora

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

Nika
Nika

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

Related Questions