Promise in a loop! How to?

I've been trying to get this bit of code to work for a while, and I just can't seem to get it. It's for a live-updating graph, that takes in an array of [x, y] for each point, so an array of arrays.

The input is a JSON list coming from a server, that updates that same JSON object (so it changes one JSON and updates only the value):

[
  {
    "Value": 10,
    "Name": "Name of variable"
  }
]

I need to extract the value only.

I tried it like this:

var getValueData = async function() {
    var valueJSON = await Promise.resolve($.getJSON( "{% url 'get_value' %}" ));
    return valueJSON[0]['Value'];
};

var data = [];
var totalPoints = 100;
var updateInterval = 1000;
var now = new Date().getTime();

function getData() {
    data.shift();

    while (data.length < totalPoints) {
        var value = [now += updateInterval,
                    getValueData().then(function(result) {
                        result;
                    })
                   ];

        data.push(value);
    };
};

Basically, getData() is trying to build an array of X = timestamp.now(), and Y = "Value" from JSON, then push that array into my "overall" data array.

Doing it this way makes value an array of [<timestamp>, Unresolved].

Doing it this way:

while (data.length < totalPoints) {
    getZScoreData().then(function (result) {
        var valueArray = [now += updateInterval, result];
        data.push(valueArray);
    });
};

Makes valueArray be an actual [<timestamp>, <JSON "value"], if I console.log(value), except this way seems to hang the server forever and consume a huge amount of RAM for the tab, as if I was doing an infinite loop (hundreds of get requests to the web server, even though the max length should 100). I'm not sure of the what's going inside the Promise to get this behavior though.

This here is the code that works in the example:

while (data.length < totalPoints) {     
    var y = Math.random() * 100;
    var value = [now += updateInterval, y];

    data.push(value);
};

It seems straightforward enough. Where it says y (in var value = [now += updateInterval, y];) make "y" get the value from my API.

I have no clue how to actually achieve this.

I'm following the example from this Flot example but I just can't manage to make it work with an actual live value from AJAX or Promise (I even tried fetch).

All examples for "live updating table" end up just using math.random(), which is pretty misleading, as it just kinda moves about, but isn't really live.

I believe I'm not resolving the promise in the loop properly, but due to lack of experience, at this point I'm not even sure what is wrong.

I'm not sure where in my code I would go Y = "live value", or whether I have to return result somewhere? I'm not familiar with Promises or AJAX that much.

Answers:

Answer

Promises in loops are rarely a good idea, you'll usually want to use Promise.all() to execute multiple Promises at once, and get their results collected in an array:

function getData() {
    // Note: Why is this here?
    data.shift();

    var promises = [];
    while (data.length < totalPoints) {
        promises.push(getValueData());
    }

    Promise.all(promises).then(results => {
        for (let result of results) {
            var value = [
                now += updateInterval,
                result
            ];
            data.push(value);
        }
    });
};

MDN has some good materials on Promises: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises

As a side note, executing 100 AJAX requests at the same time sounds pretty strenuous, even with Promise.all(). You should either try to optimize the backend if you have any influence over it, or look into Workers to execute the requests in batches.

Answer

You can generate array of Promises and then wait of resolve with Promise.all.

// Let's imitate some async operation
// It is instead of your getValueData
function twiceAsync(a) {
  // returns a promise resolving to twice argument
  return new Promise(function(ok) {
    setTimeout(function(){ok(2*a)}, 100);
  });
}

var pList = [];
for (var i = 0; i < 100; ++i) {
  // collect promises returned by twiceAsync to an array
  pList.push(twiceAsync(i));
}

Promise.all(pList) // provide array of promises to Promise.all
  .then(function(results) { // receive resolved results, they should be even numbers from 0*2 to 99*2
    console.log(results);
  })

Answer

Some things you should take in mind

  • Your getValueData is not reaching the return statement because it resolves as soon as it reach Promise.resolve. (In the my example, now getData returns a promise)
  • Use Promise.all so you don't wait for the previous one to complete before executing the next... It resolves once every promise in the raid resolves, or one of them fails (you have to .catch that).
  • Learn more about Promises, they are scary at first, but you'll end up loving em

Try this code, it may not work because I don't have the full working example, but you can get around errors probably and get it to work.

var results = [],
    totalPoints = 100,
    pointsLeft = totalPoints, // since we are using Promise.all, now we have to track this instead of results.length
    updateInterval = 1000,
    promises = [],
    getValueData = async function(timestamp) {
        await $.getJSON('yourURL').done(function (data) {
           pointsLeft-- // promise fulfilled 
           Promise.resolve({ data, timestamp })
        }).fail(Promise.reject)
    };

function getData() {
    // results.shift(); why?

    while (pointsLeft) {
        // push the promise to the stack, collect them later
        promises.push(getValueData(Date.now()))
    };


    Promise.all(promises).then(function (responses) {
        responses.forEach(function (data, timestamp) {
            results.push([timestamp, data])
        })
    }).catch(console.warn)
};

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.