Views are removed in a synchronous way causing issues with animations


#1

Hello there!

The problem occurs when you return promise from Animator that takes a lot of time to become solved. In that moment processing of other views is stopped. This causes, in my opinion, synchronization issues and unpredictable behavior (you can’t really tell when the item is going to be processed).

Example:

(Note: Following example takes issue even further.)

Imagine you write a post and you want to share it to several social networks. You have one general share button and once you press it script will try to post your new post to selected networks. So you select Facebook and Twitter from a list and click SHARE (A). Then you want to show some progress going on, while you’re posting. At the moment the post is successfully posted, you want to show checkmark (B). Once the post was posted to all selected social networks (= Twitter and Facebook) you want to show fade-out animation on those items (representing Twitter and Facebook) (C ) - items should fade-out at the same time (= together).

One way you might want to do this is to use leave event of Animator. So during (A), you will count all networks selected:

this.selectedCount = this.networks.filter( n => n.selected ).length

And (B) and (C ) you’ll solve using Animator:

function onLeave()
{
	// create deferred if not present
	if ( ! this.allFinished )
	{
		this.allFinished = new Deferred();
	}

	// increase count of completed items, and once all are completed run animation
	if ( ++this.finishedCount === this.selectedCount )
	{
		this.allFinished.resolve();
	}

	// you might want to show checkmark here as (B)

	// view is removed after all are tasks are completed and animation has finished
	return this.allFinished.promise.then( x => playAnimation() );
}

class Deferred
{
	constructor()
	{
		this.promise = new Promise(( resolve, reject ) =>
		{
			this.reject = reject;
			this.resolve = resolve;
		})
	}
}

This will end up in the cycle because the first item (let’s say Twitter) is waiting for second item (Facebook), but the second item will never run onLeave, till promise of the first item hasn’t finished.

Expected behavior

I would expect items (I refer to items because I have this problem with lists) are processed independently.


#2

Thats more of an issue how you wait for your promise completion. Check the mdn docs for Promise.all vs Promise.race where the latter would resolve as soon as any first completes.


#3

Thanks for answer,

but this not about promises that much, it is only about View management. I’m removing items from a list. But all of them require some additional async logic and the time of competition such task may vary for every item. Once completed I’ll remove the item from a list in JS. Now Binding Engine will report to Templating, that item in the array was removed, and the corresponding View should be removed. It reports to Animator, that given View is going to be removed. And now I want to pause that action and wait for all other items to be removed the same way, and play animation so they visually disappear at the same time. I provided one possible implementation of such action.

The issue is somewhere between Binding Engine and Templating, because it reports that first item is being removed (in JS), it calls Animation and it waits for Promise to be finished, preventing detection of other items that in the meantime were removed as well (in JS).

So it all falls into a situation, where the first item is waiting for others to be removed (= till Animator is called for them), but Animator is not called for them till the first item is not removed (= promise returned by Animator is not resolved).


#4

If you could create a small example it would help to understand and figure out a way to solve your issue. Thanks in advance


#5

Ok, I have prepared an example, hope it helps.

Code: https://github.com/tomasbonco/aurelia-animator-bug
You can see it live at: http://au.comper.sk/

Thanks


#6

tbh this example is so filled up with details its hard for me to get the actual idea of what you’re trying to do. There are tons of possible sideeffects (waitingRoomMaxSize, isPublishing boolean) plus we’re talking here about implementing a custom animator.
So what I think I understood is that you’re trying to let multiple actions run at the same time and manipulate the DOM asap on their own.

So instead I would perform the requests in parallel and handle your “pending” indicator on firing those actions. Once all promises are done hanlde the fade-out for all of thiem inside Promise.all. Do not actually remove any item from the list until all requests are passed, that way you do not modify the DOM and trigger unnecessary rerenders.


#7

Hello, thanks for the answer.

I thought you reply the way you replied, but I can’t implement it that way. I really tried to simplify things as possible, but I what I do in the real app is I send a request to server and server is sending me new JSON with a state. I want to reflect this state in UI. In this new state, items could be removed. So I have to solve it on Animator layer (or I think it would far easier).

So when you look at the example, it wasn’t easy for me to make it, because it’s not a trivial problem. The whole problem, in my opinion, is that Templating engine is working in a synchronous way, waiting till one promise is resolved to remove another item. I would like it to work in parallel.

I think it is important because of 2 scenarios. First I showed in the example (when you are blocked and you have no idea why untill you realize how it works under the hood), the second issue is you as a developer don’t really know how is UI going to look at any given moment. Let me explain. Imagine that you will remove 3 items, in a 600ms sequence, but animation takes 1 second. You would naturally expect that UI will finish animating after 2.2 seconds (1.2 seconds till the last animation is fired + 1s duration of animation), but in reality, it will take 3 seconds. Because of this waiting for a promise. (See picture, if it’s hard to imagine: http://au.comper.sk/au-timing.png).

Or imagine, it would be 500ms sequence instead. You would expect to see the first animation at time 0s, second at time 0.5s and the third starting at 1s. But what would you see is first animation starting at time 0s, but second and third being started together at time 1s.

I hope now you see that as a developer you really need to understand how it works under the hood, to explain what you see, and it is something different then what you would naturally expect. Plus you are limited.

Hope this helps.
Tomas


#8

Hey there, I’ve pinged you over at gitter as there seems to be more stuff to discuss, which might be easier in realtime :slight_smile: