-
-
Notifications
You must be signed in to change notification settings - Fork 208
feat(): add rxScheduleTask function #1633
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
d4687b4
db2d414
8ec9208
cdaa34d
18e9e4d
a7570a3
3f01a05
83dc4d5
31e8c6c
7628ecb
bd65ad3
3607b37
6603ea7
1ab7c31
e2ee520
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,112 @@ | ||||||
# rxScheduleTask | ||||||
|
||||||
`rxScheduleTask` provides a helper function to schedule function execution. It is a minimal building block for making performance optimizations in your code. | ||||||
|
||||||
## Motivation | ||||||
|
||||||
Chromium based browsers considers all tasks that taking more than 50ms as long tasks. If task runs more than 50ms, users will start noticing lags. Optimally all user interactions should happen at 30 fps framerate with 32ms budget per browser task. In ideal world it should be 60 fps and 16ms budget. | ||||||
|
||||||
> 💡 In reality browser has a reserved overhead of 4ms, try to stick to 28ms of work for 30 fps and 12ms for 60 fps. | ||||||
Karnaukhov-kh marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
## Scheduling mechanisms in browser | ||||||
|
||||||
Most common ways of delaying task execution are: | ||||||
Karnaukhov-kh marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
- `setTimeout` | ||||||
- `requestAnimationFrame` | ||||||
- `requestIdleCallback` | ||||||
Karnaukhov-kh marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
`rxScheduleTask` provides similar API but comes with huge benefits of notion of frame budget and priority configuration. | ||||||
Karnaukhov-kh marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
## Concurrent strategies | ||||||
|
||||||
> 💡 Under the hood all our concurrent strategies are based on MessageChannel technology. | ||||||
|
||||||
To address the problem of long tasks and help browser split the work @rx-angular/cdk provides concurrent strategies. This strategies will help browser to chunk the work into non-blocking tasks whenever it's possible. | ||||||
Karnaukhov-kh marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
You can read detailed information about concurrent strategies [here](https://github.com/rx-angular/rx-angular/blob/main/libs/cdk/render-strategies/docs/concurrent-strategies.md). | ||||||
|
||||||
## Usage examples | ||||||
|
||||||
Karnaukhov-kh marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
### Input params | ||||||
|
||||||
- Just as common delaying apis this method `accepts` a work function that should be scheduled. | ||||||
- It also accepts configuration object as an optional second parameter | ||||||
Karnaukhov-kh marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
- `strategy` which will be used for scheduling (`normal` is default, for full list of available strategies see [concurrent strategies documentation](https://github.com/rx-angular/rx-angular/blob/main/libs/cdk/render-strategies/docs/concurrent-strategies.md)) | ||||||
- `delay` which is responsible for delaying the task execution (default is 0ms) | ||||||
- `ngZone` if you want your function be executed withing ngzone (default scheduling runs out of zone) | ||||||
Karnaukhov-kh marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
### Return type | ||||||
|
||||||
Function returns a callback that you can use to cancel already scheduled tasks. | ||||||
|
||||||
### Default usage | ||||||
|
||||||
```typescript | ||||||
import { rxScheduleTask } from '@rx-angular/cdk/render-strategies'; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I don't like it that we export it from render-strategies. Because it doesn't do anything with rendering. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you think about better place? We had this discussion in old PR and decided that this is the best option we currently have. We can say the same about RxStrategyProvider, aren't we? This is a service for scheduling any type of work, but it's still in the render-strategies. Same reasoning goes to rxScheduleTask. You can make it "related" to rendering by doing rxScheduleTask(() => this.cdRef.detectChanges()) ¯_(ツ)_/¯. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we can introduce a new sub-package for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd go for it! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmmm There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would do a rethink before and maybe see what other code we want to move There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whatever we decide, please let's not have it frozen for another 1.5 years :D There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 for |
||||||
... | ||||||
|
||||||
function updateStateAndBackup<T>(data: T) { | ||||||
Karnaukhov-kh marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
this.stateService.set(data); | ||||||
|
||||||
rxScheduleTask(() => localStorage.setItem('state', JSON.stringify(state))); | ||||||
} | ||||||
``` | ||||||
|
||||||
### Usage with non-default strategy | ||||||
|
||||||
```typescript | ||||||
import { rxScheduleTask } from '@rx-angular/cdk/render-strategies'; | ||||||
... | ||||||
|
||||||
function updateStateAndBackup<T>(data: T) { | ||||||
this.stateService.set(data); | ||||||
|
||||||
rxScheduleTask( | ||||||
() => localStorage.setItem('state', JSON.stringify(state)), | ||||||
{strategy: 'idle'} | ||||||
); | ||||||
} | ||||||
``` | ||||||
|
||||||
### Usage with options | ||||||
|
||||||
```typescript | ||||||
import { rxScheduleTask } from '@rx-angular/cdk/render-strategies'; | ||||||
... | ||||||
|
||||||
function updateStateAndBackup<T>(data: T) { | ||||||
this.stateService.set(data); | ||||||
|
||||||
rxScheduleTask( | ||||||
() => localStorage.setItem('state', JSON.stringify(state)), | ||||||
{ delay: 200, zone: this.ngZone, strategy: 'idle' } | ||||||
); | ||||||
} | ||||||
``` | ||||||
|
||||||
### Cancel scheduled task | ||||||
|
||||||
```typescript | ||||||
import { rxScheduleTask } from '@rx-angular/cdk/render-strategies'; | ||||||
... | ||||||
|
||||||
let saveToLocalStorageCallback; | ||||||
|
||||||
function updateStateAndBackup<T>(data: T) { | ||||||
this.stateService.set(data); | ||||||
|
||||||
if (saveToLocalStorageCallback) { | ||||||
saveToLocalStorageCallback(); | ||||||
} | ||||||
|
||||||
saveToLocalStorageCallback = rxScheduleTask(() => | ||||||
localStorage.setItem('state', JSON.stringify(state)) | ||||||
); | ||||||
} | ||||||
``` | ||||||
|
||||||
## Links | ||||||
|
||||||
- [Detailed information about strategies](https://github.com/rx-angular/rx-angular/tree/master/libs/cdk/render-strategies) | ||||||
- [MessageChannel documentation](https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { NgZone } from '@angular/core'; | ||
import * as scheduler from '@rx-angular/cdk/internals/scheduler'; | ||
import { rxScheduleTask } from '../src'; | ||
|
||
describe('rxScheduleTask', () => { | ||
let work: jest.Mock; | ||
let ngZone: NgZone; | ||
let scheduleSpy: jest.SpyInstance; | ||
let cancelSpy: jest.SpyInstance; | ||
|
||
beforeEach(() => { | ||
work = jest.fn(); | ||
// Mocking NgZone | ||
ngZone = { run: (fn: Function) => fn() } as any; | ||
|
||
// Spying on the scheduleCallback and cancelCallback functions | ||
scheduleSpy = jest.spyOn(scheduler, 'scheduleCallback'); | ||
cancelSpy = jest.spyOn(scheduler, 'cancelCallback'); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it('should use normal strategy as default', () => { | ||
rxScheduleTask(work); | ||
expect(scheduleSpy).toHaveBeenCalledWith( | ||
expect.anything(), | ||
expect.any(Function), | ||
{ delay: undefined, ngZone: undefined } | ||
); | ||
}); | ||
|
||
it('should schedule work with the specified strategy', () => { | ||
rxScheduleTask(work, { strategy: 'low' }); | ||
expect(scheduleSpy).toHaveBeenCalledWith( | ||
expect.anything(), | ||
expect.any(Function), | ||
{ delay: undefined, ngZone: undefined } | ||
); | ||
}); | ||
|
||
it('should schedule work with the specified delay', () => { | ||
const delay = 200; | ||
rxScheduleTask(work, { delay }); | ||
expect(scheduleSpy).toHaveBeenCalledWith( | ||
expect.anything(), | ||
expect.any(Function), | ||
{ delay, ngZone: undefined } | ||
); | ||
}); | ||
|
||
it('should schedule work inside the specified NgZone', () => { | ||
rxScheduleTask(work, { ngZone }); | ||
expect(scheduleSpy).toHaveBeenCalledWith( | ||
expect.anything(), | ||
expect.any(Function), | ||
{ delay: undefined, ngZone } | ||
); | ||
}); | ||
|
||
it('should cancel the scheduled work', () => { | ||
const cancel = rxScheduleTask(work); | ||
cancel(); | ||
expect(cancelSpy).toHaveBeenCalled(); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,28 @@ | ||
export { RxStrategyProvider } from './lib/strategy-provider.service'; | ||
export { ScheduleOnStrategyOptions } from './lib/model'; | ||
export { | ||
RX_CONCURRENT_STRATEGIES, | ||
RxConcurrentStrategies, | ||
} from './lib/concurrent-strategies'; | ||
export { RX_NATIVE_STRATEGIES, RxNativeStrategies } from './lib/native-strategies'; | ||
export { | ||
RX_RENDER_STRATEGIES_CONFIG, | ||
RxRenderStrategiesConfig, | ||
} from './lib/config'; | ||
export { | ||
RxConcurrentStrategyNames, | ||
RxCustomStrategyCredentials, | ||
RxDefaultStrategyNames, | ||
RxNativeStrategyNames, | ||
RxRenderBehavior, | ||
RxRenderWork, | ||
RxStrategies, | ||
RxStrategyCredentials, | ||
RxStrategyNames, | ||
ScheduleOnStrategyOptions, | ||
} from './lib/model'; | ||
export { | ||
RX_NATIVE_STRATEGIES, | ||
RxNativeStrategies, | ||
} from './lib/native-strategies'; | ||
export { onStrategy } from './lib/onStrategy'; | ||
export { rxScheduleTask } from './lib/rx-schedule-task'; | ||
export { strategyHandling } from './lib/strategy-handling'; | ||
export { RxStrategies } from './lib/model'; | ||
export { RxStrategyNames } from './lib/model'; | ||
export { RxDefaultStrategyNames } from './lib/model'; | ||
export { RxConcurrentStrategyNames } from './lib/model'; | ||
export { RxNativeStrategyNames } from './lib/model'; | ||
export { RxCustomStrategyCredentials } from './lib/model'; | ||
export { RxStrategyCredentials } from './lib/model'; | ||
export { RxRenderBehavior } from './lib/model'; | ||
export { RxRenderWork } from './lib/model'; | ||
export { RX_RENDER_STRATEGIES_CONFIG, RxRenderStrategiesConfig } from './lib/config'; | ||
export { RxStrategyProvider } from './lib/strategy-provider.service'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { NgZone } from '@angular/core'; | ||
import { | ||
PriorityLevel, | ||
cancelCallback, | ||
scheduleCallback, | ||
} from '@rx-angular/cdk/internals/scheduler'; | ||
import { RxConcurrentStrategyNames } from './model'; | ||
|
||
type StrategiesPriorityRecord = Record< | ||
RxConcurrentStrategyNames, | ||
PriorityLevel | ||
>; | ||
|
||
const strategiesPrio: StrategiesPriorityRecord = { | ||
immediate: PriorityLevel.ImmediatePriority, | ||
userBlocking: PriorityLevel.UserBlockingPriority, | ||
normal: PriorityLevel.NormalPriority, | ||
low: PriorityLevel.LowPriority, | ||
idle: PriorityLevel.IdlePriority, | ||
}; | ||
|
||
const defaultStrategy: keyof StrategiesPriorityRecord = 'normal'; | ||
|
||
/** | ||
* @description | ||
* This function is used to schedule a task with a certain priority. | ||
* It is useful for tasks that can be done asynchronously. | ||
* | ||
* ```ts | ||
* const task = rxScheduleTask(() => localStorage.setItem(state, JSON.stringify(state)); | ||
Karnaukhov-kh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* ``` | ||
*/ | ||
export const rxScheduleTask = ( | ||
work: (...args: any[]) => void, | ||
{ | ||
strategy = defaultStrategy, | ||
delay, | ||
ngZone, | ||
}: { | ||
strategy?: keyof StrategiesPriorityRecord; | ||
delay?: number; | ||
ngZone?: NgZone; | ||
} = {} | ||
) => { | ||
const task = scheduleCallback(strategiesPrio[strategy], () => work(), { | ||
delay, | ||
ngZone, | ||
}); | ||
return () => { | ||
cancelCallback(task); | ||
}; | ||
}; |
Uh oh!
There was an error while loading. Please reload this page.