Our pure JavaScript Scheduler component


Post by jmeire »

Hi

I discovered the following behaviour:
When the scheduler is loaded and you navigate to another component, everything is fine.
But when you navigate while the scheduler is still fetching data an error occurs.

Example gif:
hLyy8HcJ5q.gif
hLyy8HcJ5q.gif (206.65 KiB) Viewed 2860 times

Full error:
Error: Uncaught (in promise): TypeError: _0x259982.through[_0x4dce(...)] is not a function
TypeError: _0x259982.through[_0x4dce(...)] is not a function
    at scheduler.module.js:9
    at Array.forEach (<anonymous>)
    at ResourceStore.trigger (scheduler.module.js:9)
    at scheduler.module.js:21
    at ZoneDelegate.invoke (zone-evergreen.js:365)
    at Object.onInvoke (core.js:40794)
    at ZoneDelegate.invoke (zone-evergreen.js:364)
    at Zone.run (zone-evergreen.js:124)
    at zone-evergreen.js:851
    at ZoneDelegate.invokeTask (zone-evergreen.js:400)
    at resolvePromise (zone-evergreen.js:793)
    at zone-evergreen.js:858
    at ZoneDelegate.invokeTask (zone-evergreen.js:400)
    at Object.onInvokeTask (core.js:40772)
    at ZoneDelegate.invokeTask (zone-evergreen.js:399)
    at Zone.runTask (zone-evergreen.js:168)
    at drainMicroTaskQueue (zone-evergreen.js:570)
    at ZoneTask.invokeTask [as invoke] (zone-evergreen.js:485)
    at invokeTask (zone-evergreen.js:1596)
    at XMLHttpRequest.globalZoneAwareCallback (zone-evergreen.js:1633)

Example project:
unCaughtInPromiseError.zip
(17.29 MiB) Downloaded 113 times
How can I handle or prevent this error?

Kind regards
jmeire

Post by saki »

If you use Angular router with default config then the scheduler is destroyed and then re-created. It looks like some requests are not aborted properly. It may be a bug but we will first analyze your code.

If you do not want to destroy and re-create scheduler on each navigation then you can use our approach from our routing example where we have custom routing strategy implemented.

Post by saki »

It doesn't look like a bug in scheduler. The requests are sent by your code so you need to abort them before destroying scheduler page. Your bryntum.component should probably implement onDestroy method and there you should abort all pending requests.

Post by jmeire »

Okay, thanks for analyzing this!

The code that is used to fetch the data with the AjaxHelperOverride was based on this topic a couple of weeks ago:
viewtopic.php?f=44&t=13217&p=68831#p68831

Now I tried to call a couple of things in the ngOnDestroy of my component but none of it seems to prevent the error:
I tried:
scheduler.destroy();
scheduler.ngOnDestroy()
scheduler.resourceStrore.destroy()

How can I abort these requests before destroying the component?
It would be very usefull if this could be automatically destroyed/aborted when destroying the component where the scheduler is used.

Post by mats »

Can you reproduce this on our vanilla JS examples? Any step by step for us to reproduce it locally?

Post by jmeire »

Hi

The issue is triggered when clicking for example on a routerLink while the scheduler is still loading.
So i guess the scheduler is not correctly destroyed which is difficult to implement in a vanilla js example.
It also might be something in the resource store that is not correctly destroyed.

But i again added a zip with all unnecessary code removed.
destroyIssue.zip
(17.29 MiB) Downloaded 142 times

Here is what I did to reproduce it step by step:
- Add bry scheduler component
- Init all its needed properties
- Init the resourceStore with autoLoad true and a read url
- Add an AjaxHelperOverride as described in this topic:
viewtopic.php?f=44&t=13217&p=68870&hilit=load+method+call+starts#p68870
- Add example of promise inside the override (this will be an api call in real world example)
- Add settimeout to mock time of call duration (time that scheduler is in loading state)
- When navigating (to trigger destroy) while the scheduler is still loading, this triggers the error.

As you will see in the example zip, there is a catch on every promise with a log, but none is triggered.

I think, when leaving the component/destroying the component, this should all be handled automatically and I should not have to worry about this behavior.
If I have to do something extra to clean this up in the onDestroy method, that's fine but then I would like to know what.

Thanks in advance for looking into this!

Kind regards
jmeire

Post by saki »

I made some tests and I found the following sequence of events:
  1. load starts, promises unresolved (yet) Ajax requests pending
  2. navigation occurs
  3. scheduler is destroyed in ngOnDestroy of the wrapper (SchedulerComponent)
  4. response from the server arrives
  5. the scheduler code tries to run various updates with new data on destroyed scheduler
  6. that results in the problem
Now, the solution is to cancel/abort all pending requests before the scheduler is destroyed. I was trying to do it but your requests are wrapped in AjaxHelper so not easily accessible from your component.

Anyway, the code could be as follows:
  private cleanupSub:Subscription;
  ngOnInit() {
    const cleanup = (e:NavigationStart) => {
      if(e instanceof NavigationStart ) {
        console.log('aborting pending requests');
        // cancel/abort all pending requests here
      }
    };
    this.cleanupSub = this.router.events.subscribe(cleanup);
  }
  ngOnDestroy() {
    if(this.cleanupSub) {
      this.cleanupSub.unsubscribe();
    }
  }
NavigationStart and Subscribe classes must be imported and router injected:
import { Router, NavigationStart } from '@angular/router';
import { Subscription } from 'rxjs';

// ...

constructor(private router:Router) {
    // the rest of constructor's code
}

Post by jmeire »

Hi

Thanks for looking into this.

I currently fixed it like this:
in constructor:
this.destroy$ = new Subject();
(window as any).planningDestroy = this.destroy$;
on destroy:
public ngOnDestroy(): void {
	this.destroy$.next();
	this.destroy$.complete();
}
And in the AjaxHelperOverride we use a takeUntil of (window as any).planningDestroy on a subscription of a service function, that does an api call.
When the component is destroyed the subscribe body will never be executed.

But still it feels weird to do things like that.
I would like to get a solution that is more 'clean' if possible, without the window object.

Thanks for the help so far!

Kind regards
jmeire

Post by saki »

If you can live with out crudManager you shouldn't need to do this handling. Crud manager knows about requests it sends so it can abort them and uninstall all listeners on destroy.

If you create some async tasks that use scheduler outside of scheduler and scheduler doesn't know about them then you have to take care about the proper destroy process.

Post Reply