1

Ive posted other questions but feel I should simplify things

I have one function that sets a context and calls a second function to draw the lines on the context.

I have this:

var arr = [];

which is populated like this:

arr = [context,pre];

while pre looks like pre = [[{x:n,y:m}],[{x:j,y:k}]];

So, basically, I have an array, pre, containing arrays of coordinates. That array is pushed with a context into arr

arr is returned and pushed into a final array, lets say final_arr, which now should look like this: final_arr = [[context1,pre1],[context2,pre2],...]

My goal is to loop through final_arr and draw lines on different contexts, determined by the context in the array. For example, the first iteration will access final_arr[0] and contain context1,pre1. These two values are sent to a function, wrap(context, pre) that returns a promise. Inside this wrap function, another function is called, animate(pre[i]). this function takes each element in pre, which corresponds to an array of coordinates, and actually draws the line using animation frames. animate() also returns a promise.

Currently, only one of the paths is being drawn, which seems to be because only one value of final_arr is being used, even though I am iterating through it

My attempts to iterate:

final_arr.reduce((a,c) => a.then(() => wrap(c[0],c[1])), Promise.resolve());

and

var temp = Promise.resolve();
var i = 0;
for (i = 0; i < arr.length; i++){
    //window.alert(arr[i].length)
    var ct = arr[i][0];
    var line = arr[i][1];
    temp.then(() => wrap(ct,line));
}

and here are the functions being called:

/*
*   Animation function draws a line between every point
*/              
var animate = function(p){
    return new Promise(function(resolve) {
        t = 1;
        var runAnimation = function(){
            if(t<p.length){
                context.beginPath();
                context.moveTo(p[t-1].x,p[t-1].y);
                context.lineTo(p[t].x,p[t].y);
                context.stroke();
                t++;
                requestAnimationFrame(function(){runAnimation()});
            } else {
                resolve()
            }
        };
        runAnimation();
    });
}
function wrap(ctx, lines){
    return new Promise(function(resolve) {
        var counter = 0;
        t = 1;
        var getAnimation = function(){
            if(counter < lines.length){
                context = ctx;
                lines.reduce((a, c) => a.then(() => animate(c)), Promise.resolve());
                counter++;
            } else {
                resolve()
            }
        };
        getAnimation();
    });
}

The context variable set in wrap is a global variable for the js file

I hope the question asked this way provides clarity as to what I am having a problem with

Thank you for any help

Edit:

Attempted fiddle

Edit2:

Oddly enough this works

if(final_arr.length == 1){
                    wrap(final_arr[0][0], final_arr[0][1]);
                } else if (final_arr.length == 2){
                    wrap(final_arr[0][0], final_arr[0][1]).then(wrap(final_arr[1][0], final_arr[1][1]));
                } else if (final_arr.length == 3){
                    wrap(final_arr[0][0], final_arr[0][1]).then(wrap(final_arr[1][0], final_arr[1][1])).then(wrap(final_arr[2][0], final_arr[2][1]));
                }

But when using this, the lines are drawn at the same time (which is okay, but not preferred)

ChrisM
  • 125
  • 14
  • One quick thing: You don't declare `t` inside the function, so it is a global used across all your contexts. Is that causing your issues? – Scott Sauyet Jul 20 '18 at 18:32
  • a fiddle to play with would be nice, but i think you could populate final_arr with the warp promise function and use Promise.All – enno.void Jul 20 '18 at 18:33
  • @ScottSauyet I dont believe so, since for one it is reset to 1 every call to wrap (I believe?) and it is reset to 1 when the function that returns the `arr` variable is called (not included because it seemed to cause confusion in my last post). The idea is that it is reset to one in animation so each of the elements in `pre` start at the beginning, and set to 1 in `wrap` so any leftover from the last iteration of `animate` is fixed – ChrisM Jul 20 '18 at 18:35
  • @mr.void I am working on a fiddle, for some reason it doesnt want to run anything... Im 4 days new to JS so its been interesting. I looked into promise.all but was confused how it works with having to call a function to do something first...mind an example I could try? – ChrisM Jul 20 '18 at 18:37
  • 1
    I do think a Fiddle would help. I still don't understand `t`. It looks like each animation would interfere with every other one. And I don't understand `getAnimation` either. This, BTW, is fairly sophisticated code for someone 4 days into the language! – Scott Sauyet Jul 20 '18 at 18:39
  • @ScottSauyet Ive been programming for a while, but the language itself is new (and boy oh boy theres a lot). Fiddle is on its way I hope... seems difficult to reproduce in a fiddle for some reason. Im not 100% sure how `requestAnimationFrame` works, but my understanding is it is an async function that runs in parallel, so by using promises we make sure it happens in series, which I thought would take care of `t` and protect it from other animations, because they cant run. I should mention this code works when `final_arr.length` is only 1 – ChrisM Jul 20 '18 at 18:45
  • @ChrisM here a simple promise all example: https://jsfiddle.net/f5d8cozp/7/ – enno.void Jul 20 '18 at 18:53
  • @mr.void would `let myPromiseFn = function(yyy){ return new Promise((res,rej) => { setTimeout(x => { res(yyy)}, 1000); }) }` format be used for `wrap` in my case? Also, was that fiddle supposed to show anything when I hit run? because the white box stays empty just like it did for mine so I want to make sure there isnt an issue with fiddle for me – ChrisM Jul 20 '18 at 18:59
  • yes, because its returns a promise; look at the developer-console ... – enno.void Jul 20 '18 at 19:00
  • @mr.void Ah I see it now. Couple questions: does `Promise.all([myPromiseFn(1),myPromiseFn(2),myPromiseFn(3)]).then(x => console.log(x))` run each function, get the returned value of `x`, put them in the array, then the call to `then` resolves the promises and prints `x` in the console using `console.log(x)`? – ChrisM Jul 20 '18 at 19:08
  • what do you get in `console.log(t, p)` inside `runAnimation` for 2 items in `final_arr`, each with 3 points plz? does it get called 4 times with correct values? – Aprillion Jul 20 '18 at 19:27
  • @Aprillion with that added, it looks like `t` goes from 1 to the right number, but it only does so once, which leads me to believe that `animate()` is only being called once – ChrisM Jul 20 '18 at 19:36
  • @mr.void: If I understand the question properly, `Promise.all` is not going to help. I think these animations are supposed to run one after another. For this, the `lines.reduce(..., Promise.resolve())` looks correct. – Scott Sauyet Jul 20 '18 at 19:45
  • @ScottSauyet You are correct, they have to run on after another. Also I am still trying to create a super basic fiddle that simulates whats going on here but I am having a lot of trouble getting anything to run on it... I am going to post it so you can look while I attempt to fix it – ChrisM Jul 20 '18 at 19:46
  • if you actually checked that `animate` is only called once after you implemented code from my answer - `lines.forEach(p => animate(p, ctx))` - then the only possible option is that `lines` contains only 1 line and that `wrap` is either only called once or never gets inside the `if` statement at other times... – Aprillion Jul 20 '18 at 19:50
  • @Aprillion well as I said in the original question, I believe the culprit is the line `final_arr.reduce((a,c) => a.then(() => wrap(c[0],c[1])), Promise.resolve());` not iterating the array and calling `wrap` as it should. In testing, when `final_arr` contains 2 elements, `[context1, pre1]` and `[context2,pre2]`, it does contain those (seen with `console.log(final_arr)`) but only one of the `[context,pre]` pairs is ever seen inside wrap for some reason (again using `console.log(lines)` – ChrisM Jul 20 '18 at 19:57
  • I edited my answer - the promise returned by `wrap` never calls `resolve()` if there is > 0 lines – Aprillion Jul 20 '18 at 20:00
  • Any more luck on the Fiddle? The advice in the [Help Center's](https://stackoverflow.com/help) [How to create a Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve) is extremely useful to help you figure out the issue or help us help you do so. – Scott Sauyet Jul 20 '18 at 20:00
  • @ScottSauyet did you take a look at the one I posted? The fiddle contains code that is a minimized version of this, but I just cant get it to run normally...Still working on it – ChrisM Jul 20 '18 at 21:02

1 Answers1

1

edit: just spotted the missing resolve inside the if statement of wrap => the returned Promise will never be resolved...

I recommend to start with a much simpler version of wrap before making any micro-optimizations:

function wrap(ctx, lines){
  return new Promise(function(resolve) {
    lines.forEach(p => animate(p, ctx));
    resolve();
  });
}

callbacks to requestAnimationFrame are called after finishing all microtasks (i.e. after all Promises) - see When will requestAnimationFrame be executed?

so the value of the global variable context will be the same for all of the callbacks, i.e. the same line drawn multiple times or a race condition or something depending on internals of the context

I would get rid of globals, using only function params and locals:

var animate = function(p, ctx) {
  var t ...
  ... ctx.beginPath()
Aprillion
  • 21,510
  • 5
  • 55
  • 89
  • No change after implementing this unfortunately. Will I need to change variable names to move away from the global or does JS create a new variable with the same name but new scope? – ChrisM Jul 20 '18 at 19:04
  • uhm, the point of local variables is that they don't have to be globally unique, so yes, you can use the same name in different places... – Aprillion Jul 20 '18 at 19:24
  • I figured as its the same in other languages, but doesnt hurt to ask – ChrisM Jul 20 '18 at 19:26
  • the `wrap` function uses an `if(){}else{}`, and the `else` contains the `resolve()`, wouldnt that get called when `counter` is greater than `lines.length`? – ChrisM Jul 20 '18 at 21:01
  • it would, but `getAnimation` is only executed once per one `wrap` call, so when the `if` branch is executed, then the `else` is not – Aprillion Jul 21 '18 at 07:32
  • WOW! I completely overlooked the fact that it wasnt getting called recursively haha. That fixed it, thank you! – ChrisM Jul 22 '18 at 19:52
  • I should clarify that in my case it didnt need to to be called recursively, but in `animate` I did, so when I wrote `wrap` I copied `animate` because it was a promise that was working, and I am new to them so I used it as a template. When I wrote `wrap` I removed the recursive call because I knew it wasnt needed, and overlooked the fact that it prevented the else from being called – ChrisM Jul 22 '18 at 21:04
  • Out of curiosity, any idea how to make it draw one line then another? currently, they are being drawn all at once – ChrisM Jul 23 '18 at 00:14
  • 1
    Hint: Don't use `lines.forEach(...)`. A one-liner of the form `return lines.reduce(...)` should do it. – Roamer-1888 Jul 23 '18 at 01:09
  • @Roamer-1888 Yeah i forgot to mention I didnt use `forEach` and instead stuck with `reduce`. Why the `return` though? – ChrisM Jul 23 '18 at 17:39
  • Because the `new Promise()` wrapper is unnecessary. Simply return the promise returned by `lines.reduce(...)`. – Roamer-1888 Jul 23 '18 at 18:05
  • @Roamer-1888 Is that because `animate` is already a promise, so returning the results of `lines.reduce` will return the promise? – ChrisM Jul 24 '18 at 07:15
  • Yes, in part. .reduce's callback is always written to return, at each iteration, something that will be passed to the next iteration. Whatever is returned by the last iteration is returned by the entire `array.reduce(callback, initialValue)` expression. The `initialValue` is also (typically) the same type as the returned value. So it's like putting something on a merry-go-round, modifying it at each revolution, eventually stopping the merry-go-round and allowing the final, modified version of the original something to get off. If the "something" is a promise, then a promise is returned. – Roamer-1888 Jul 24 '18 at 11:10
  • @Roamer-1888 do you have a working example please? because the Promises that do not wait for anything will be executed in the same order and with the same zero delay after each other when chained (via reduce) as if started all at the same time (via forEach)... you would need a `setTimeout(resolve, 1/60)` inside `animate` to make sure that it's not all executed before the first available animation frame (then it would make a difference whether you chain them or start all at once) – Aprillion Jul 25 '18 at 18:15