https://github.com/oe/async-task-schedule
schedule async tasks, remove redundant with cache support
https://github.com/oe/async-task-schedule
Last synced: 4 months ago
JSON representation
schedule async tasks, remove redundant with cache support
- Host: GitHub
- URL: https://github.com/oe/async-task-schedule
- Owner: oe
- Created: 2022-09-22T12:57:34.000Z (almost 4 years ago)
- Default Branch: main
- Last Pushed: 2024-11-20T16:59:55.000Z (over 1 year ago)
- Last Synced: 2026-01-13T23:03:24.926Z (6 months ago)
- Language: TypeScript
- Size: 156 KB
- Stars: 2
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: readme.md
Awesome Lists containing this project
README
# async-task-schedule
schedule async tasks in order
## Features
* remove duplicated tasks' requests
* combine tasks' requests in same time and do all together
* can prevent massive requests at same time, make them execute one group by one group
* cache result and can specify validity
## Install
```sh
yarn add async-task-schedule
# or with npm
npm install async-task-schedule -S
```
## Usage
```ts
import TaskSchedule from 'async-task-schedule'
let count = 0
const taskSchedule = new TaskSchedule({
doTask: async (name: string) => {
count += 1
return `${name}${count}`
},
// or use this will do the same
// batchDoTasks: async (names: string[]) => {
// count += 1
// return names.map((n) => `${n}${count}`)
// },
})
taskSchedule.dispatch(['a', 'b']).then(console.log)
taskSchedule.dispatch(['b', 'c']).then(console.log)
taskSchedule.dispatch(['d', 'c']).then(console.log)
taskSchedule.dispatch('c').then(console.log)
// batchDoTasks will be only called once
```
**NOTICE**: in following example, tasks won't combine
```ts
// batchDoTasks will be executed 3 times due to javascript language features
const result1 = await taskSchedule.dispatch(['a', 'b'])
const result2 = await taskSchedule.dispatch(['b', 'c'])
const result3 = await taskSchedule.dispatch(['d', 'c'])
const result4 = await taskSchedule.dispatch('c')
```
## API
### constructor(options: ITaskScheduleOptions)
options define:
```ts
// `Task` for single task's parameters
// `Result` for single task's response
interface ITaskScheduleOptions {
/**
* action to do batch tasks, can be async or sync function
* Task: single task request info
* Result: single task success response
*
* batchDoTasks should receive multitasks, and return result or error in order
* one of batchDoTasks/doTask must be specified, batchDoTasks will take priority
*/
batchDoTasks: (tasks: Task[]) => Promise> | Array
/**
* action to do single task, can be async or sync function
* one of batchDoTasks/doTask must be specified, batchDoTasks will take priority
*/
doTask?: (task: Task) => Promise | Result
/**
* check whether two tasks are equal
* it helps to avoid duplicated tasks
* default: AsyncTask.isEqual (deep equal)
*/
isSameTask?: (a: Task, b: Task) => boolean
/**
* max task count for batchDoTasks, default unlimited
* undefined or 0 for unlimited
*/
maxBatchCount?: number
/**
* batch tasks executing strategy, default parallel
* only works if maxBatchCount is specified and tasks more than maxBatchCount are executed
*
* parallel: split all tasks into a list stride by maxBatchCount, exec them at the same time
* serial: split all tasks into a list stride by maxBatchCount, exec theme one group by one group
* if serial specified, when tasks are executing, new comings will wait for them to complete
* it's especially useful to cool down task requests
*/
taskExecStrategy: 'parallel' | 'serial'
/**
* task waiting strategy, default to debounce
* throttle: tasks will combined and dispatch every `maxWaitingGap`
* debounce: tasks will combined and dispatch util no more tasks in next `maxWaitingGap`
*/
taskWaitingStrategy: 'throttle' | 'debounce'
/**
* task waiting time in milliseconds, default 50ms
* differently according to taskWaitingStrategy
*/
maxWaitingGap: number
/**
* task result caching duration(in milliseconds), default to 1000ms (1s)
* > `undefined` or `0` for unlimited
* > set to minimum value `1` to disable caching
* > `function` to specified specified each task's validity
*
* *cache is lazy cleaned after invalid*
*/
invalidAfter?: number | ((task: Task, result: Result | Error) => number)
/**
* retry failed tasks next time after failing, default true
*/
retryWhenFailed?: boolean
}
```
example:
```ts
import TaskSchedule from 'async-task-schedule'
const taskSchedule = new TaskSchedule({
doTask(n) {
console.log(`do task with ${n}`)
return n * n
},
invalidAfter: 0
})
const result = await taskSchedule.dispatch([1,2,3,1,2])
// get first result
const resultOf1 = result[0] // 1
// doTask won't be called
const result11 = await taskSchedule.dispatch(1) // 1
// clean all cached result
taskSchedule.cleanCache()
// doTask will be call again
const result12 = await taskSchedule.dispatch(1) // 1
```
### dispatch(tasks: Task[]):Promise>
dispatch multitasks at a time, will get response with corresponding order of `tasks`
this method won't throw any error, it will fulfil even partially failed, you can check whether its success by `response instanceof Error`
```ts
import TaskSchedule from 'async-task-schedule'
const taskSchedule = new TaskSchedule({
doTask(n) {
console.log(`do task with ${n}`)
if (n % 2) throw new Error(`${n} is unsupported`)
return n * n
},
invalidAfter: 0
})
const result = await taskSchedule.dispatch([1,2,3,1,2])
// get first result
const resultOf1 = result[0] // 1
// second result is error
const isError = result[1] instanceof Error // error object
try {
// will throw an error
const result2 = await taskSchedule.dispatch(2) // 1
} catch(error) {
console.warn(error)
}
```
### dispatch(tasks: Task):Promise
dispatch a task, will get response if success, or throw an error
```ts
import TaskSchedule from 'async-task-schedule'
const taskSchedule = new TaskSchedule({
doTask(n) {
console.log(`do task with ${n}`)
if (n % 2) throw new Error(`${n} is unsupported`)
return n * n
},
invalidAfter: 0
})
const result1 = await taskSchedule.dispatch(1) // 1
try {
const result2 = await taskSchedule.dispatch(2),
} catch(error) {
console.warn(error)
}
```
### cleanCache
clean cached tasks' result, so older task will trigger new request and get fresh response.
attention: this action may not exec immediately, it will take effect after all tasks are done
```ts
import TaskSchedule from 'async-task-schedule'
const taskSchedule = new TaskSchedule({
doTask(n) {
console.log(`do task with ${n}`)
return n * n
},
invalidAfter: 0
})
await Promise.all([
taskSchedule.dispatch([1, 2, 3, 1, 2]),
taskSchedule.dispatch([1, 9, 10, 12, 22]),
])
// clean all cached result
taskSchedule.cleanCache()
// task will execute again
const result = await taskSchedule.dispatch(1),
```
## utils methods
there are some utils method as static members of `async-task-schedule`
### isEqual(a: unknown, b: unknown): boolean
check whether the given values are equal (with deep comparison)
```ts
import TaskSchedule from 'async-task-schedule'
TaskSchedule.isEqual(1, '1') // false
TaskSchedule.isEqual('1', '1') // true
TaskSchedule.isEqual(NaN, NaN) // true
TaskSchedule.isEqual({a: 'a', b: 'b'}, {b: 'b', a: 'a'}) // true
TaskSchedule.isEqual({a: 'a', b: 'b', c: {e: [1,2,3]}}, {b: 'b', c: {e: [1,2,3]}, a: 'a'}) // true
TaskSchedule.isEqual({a: 'a', b: /acx/}, {b: new RegExp('acx'), a: 'a'}) // true
```
you can use it to check whether two tasks are equal / find specified task
## Receipts
### how to integrate with existing code
what you need to do is to wrap your existing task executing function into a new `batchDoTasks`
#### example 1: cache `fetch`
suppose we use browser native `fetch` to send request, we can do so to make an improvement:
```ts
const fetchSchedule = new TaskSchedule({
async doTask(cfg: {resource: string, options?: RequestInit}) {
return await fetch(cfg.resource, cfg.options)
},
// 0 for forever
// set a minimum number 1 can disable cache after 1 millisecond
invalidAfter(cfg, result) {
// cache get request for 3s
if (!cfg.options || !cfg.options.method || !cfg.options.method.toLowerCase() === 'get') {
// cache sys static config forever
if (/\/sys\/static-config$/.test(cfg.resource)) return 0
return 3000
}
// disable other types request
return 1
}
})
const betterFetch = (resource: string, options?: RequestInit) => {
return fetchSchedule.dispatch({resource, options})
}
// than you can replace fetch with betterFetch
```
with those codes above:
1. you can remove redundant request(requests with same parameters at same time will be reduced to one, this may have some side effects)
2. get request can be cached in a short time
#### example 2: deal with `getUsers`
suppose we have a method `getUsers` defined as follows:
```ts
// support max 5 users at a time
getUsers(userIds: string[]) => Promise<[{code: string, message: string, id?: string, name?: string, email?: string}]>
```
then we can implement a batch version:
```ts
async function batchGetUsers(userIds: string[]): Promise> {
// there is no need to try/catch, errors will be handled properly
const users = await getUsers(userIds)
// convert invalid users to error
return users.map(user => (user.code === 'failed' ? new Error(user.message) : user))
}
const getUserSchedule = new TaskSchedule({
maxBatchCount: 5,
batchDoTasks: batchGetUsers,
// cache user info forever
invalidAfter: 0,
})
const result = await Promise.all([
getUserSchedule.dispatch(['user1', 'user2', 'user3', 'user4', 'user5', 'user6']),
getUserSchedule.dispatch(['user3', 'user2'])
getUserSchedule.dispatch(['user6', 'user7', 'user8', 'user9', 'user10'])
getUserSchedule.dispatch(['user2', 'user6', 'user9'])
])
// only 2 requests will be sent via getUsers with userIds ['user1', 'user2', 'user3', 'user4', 'user5'] and ['user6', 'user7', 'user8', 'user9', 'user10']
// request combine won't works when using await separately
const result1 = await getUserSchedule.dispatch(['user1', 'user2'])
const result2 = await getUserSchedule.dispatch(['user3', 'user2'])
```
If you got a batch version function, you just need to make sure it throw an error when error occurred.
### how to cool down massive requests at the same time
by setting `taskExecStrategy` to `serial` and using smaller `maxBatchCount`(you can even set it to `1`), you can achieve this easily
```ts
const taskSchedule = new TaskSchedule({
...,
taskExecStrategy: 'serial',
maxBatchCount: 2,
})
```