async-task-schedule
Advanced tools
Comparing version 0.0.2 to 0.0.3
@@ -5,5 +5,6 @@ class $d415641d0cfd8c85$export$2e2bcd8739ae039 { | ||
*/ static defaultOptions = { | ||
isSameTask: (a, b)=>a === b, | ||
isSameTask: $d415641d0cfd8c85$export$2e2bcd8739ae039.isEqual, | ||
taskExecStrategy: "parallel", | ||
maxWaitingGap: 50, | ||
invalidAfter: 1000, | ||
taskWaitingStrategy: "debounce", | ||
@@ -24,6 +25,5 @@ retryWhenFailed: true | ||
this.maxWaitingGap = userOptions.maxWaitingGap; | ||
// @ts-ignore | ||
this.taskWaitingStrategy = userOptions.taskWaitingStrategy; | ||
this.batchDoTasks = userOptions.batchDoTasks; | ||
// @ts-ignore | ||
if (!userOptions.batchDoTasks && !userOptions.doTask) throw new Error("one of batchDoTasks / doTask must be specified"); | ||
this.batchDoTasks = userOptions.batchDoTasks || $d415641d0cfd8c85$export$2e2bcd8739ae039.wrapDoTask(userOptions.doTask); | ||
this.taskExecStrategy = userOptions.taskExecStrategy; | ||
@@ -38,4 +38,4 @@ this.retryWhenFailed = userOptions.retryWhenFailed; | ||
const result = this.tryGetTaskResult(tasks); | ||
if (result instanceof Error) throw result; | ||
return result; | ||
if (result instanceof Error) return Promise.reject(result); | ||
return Promise.resolve(result); | ||
} catch (error) { | ||
@@ -77,5 +77,9 @@ // not found | ||
]; | ||
// 去除掉正在等待的、正在处理的以及已经成功的 | ||
// remove duplicated tasks in itself | ||
myTasks = myTasks.filter((task, idx)=>idx === myTasks.findIndex((t)=>this.isSameTask(t, task))); | ||
// remove pending tasks | ||
if (this.pendingTasks.length) myTasks = myTasks.filter((f)=>!this.hasTask(this.pendingTasks, f)); | ||
// remove doing tasks | ||
if (myTasks.length && this.doingTasks.length) myTasks = myTasks.filter((f)=>!this.hasTask(this.doingTasks, f)); | ||
// remove done tasks | ||
if (myTasks.length) myTasks = myTasks.filter((f)=>!this.getTaskResult(f)); | ||
@@ -96,9 +100,9 @@ if (!myTasks.length) return; | ||
* if taskExecStrategy is parallel then do it immediately, | ||
* otherwise waiting util taskQueue is empty | ||
* otherwise waiting util doingTasks is empty | ||
*/ tryTodDoTasks() { | ||
// should exec in serial, and still has executing tasks | ||
if (this.taskExecStrategy === "serial" && this.taskQueue.length) { | ||
if (this.taskExecStrategy === "serial" && this.doingTasks.length) { | ||
clearTimeout(this.delayTimeoutId); | ||
// wait a moment then check again | ||
this.delayTimeoutId = setTimeout(this.tryTodDoTasks, 100); | ||
this.delayTimeoutId = setTimeout(this.tryTodDoTasks, 50); | ||
} else this.doTasks(); | ||
@@ -126,25 +130,17 @@ } | ||
else { | ||
try { | ||
const allResponse = await Promise.all(tasksGroup.map((taskList)=>$d415641d0cfd8c85$export$2e2bcd8739ae039.wrapPromise(this.batchDoTasks(taskList)))); | ||
allResponse.forEach((result, index)=>{ | ||
this.updateResultMap(tasksGroup[index], // @ts-ignore | ||
result.status === "rejected" ? $d415641d0cfd8c85$export$2e2bcd8739ae039.wrapError(result.reason) : result.value); | ||
}); | ||
} catch (error1) { | ||
this.updateResultMap(tasks, $d415641d0cfd8c85$export$2e2bcd8739ae039.wrapError(error1)); | ||
} | ||
const allResponse = await Promise.all(tasksGroup.map((taskList)=>$d415641d0cfd8c85$export$2e2bcd8739ae039.runTaskExecutor(this.batchDoTasks, taskList))); | ||
allResponse.forEach((result, index)=>{ | ||
this.updateResultMap(tasksGroup[index], result.status === "rejected" ? $d415641d0cfd8c85$export$2e2bcd8739ae039.wrapError(result.reason) : result.value); | ||
}); | ||
this.checkAllTasks(); | ||
this.removeDoneTasks(tasks); | ||
} | ||
// remove all unresolved tasks | ||
this.cleanupTasks(); | ||
} | ||
/** | ||
* 检查所有任务 | ||
* @param succeed 获取成功的结果对象 | ||
* @param failed 获取失败的 fileId 数组 | ||
*/ checkAllTasks(defaultResult) { | ||
* check all tasks, try to resolve | ||
*/ checkAllTasks() { | ||
this.taskQueue.forEach((taskItem)=>{ | ||
try { | ||
const result = this.tryGetTaskResult(taskItem.tasks, defaultResult); | ||
const result = this.tryGetTaskResult(taskItem.tasks); | ||
// eslint-disable-next-line no-param-reassign | ||
@@ -161,12 +157,14 @@ taskItem.isDone = true; | ||
} | ||
tryGetTaskResult(tasks, defaultResult) { | ||
/** | ||
* get result list of given tasks | ||
* throw error when not found(to make it easier to distinct from falsy results) | ||
* @param tasks tasks to check | ||
* @param defaultResult default result if not found | ||
*/ tryGetTaskResult(tasks) { | ||
// no cached data and no default result provided | ||
if (!this.doneTaskMap.length && !defaultResult) throw new Error("no done task"); | ||
if (!this.doneTaskMap.length) throw new Error("no done task"); | ||
if (Array.isArray(tasks)) { | ||
const result = []; | ||
return tasks.reduce((acc, task)=>{ | ||
const val = this.getTaskResult(task) || (defaultResult ? [ | ||
task, | ||
defaultResult | ||
] : false); | ||
const val = this.getTaskResult(task) || false; | ||
if (!val) throw new Error("not found"); | ||
@@ -177,6 +175,3 @@ acc.push(val); | ||
} | ||
const val = this.getTaskResult(tasks) || (defaultResult ? [ | ||
tasks, | ||
defaultResult | ||
] : false); | ||
const val = this.getTaskResult(tasks) || false; | ||
if (!val) throw new Error("not found"); | ||
@@ -191,3 +186,2 @@ return val[1]; | ||
]; | ||
return undefined; | ||
} | ||
@@ -198,3 +192,3 @@ hasTask(list, task) { | ||
removeDoneTasks(tasks) { | ||
this.doingTasks = this.doingTasks.filter((f)=>this.hasTask(tasks, f)); | ||
this.doingTasks = this.doingTasks.filter((f)=>!this.hasTask(tasks, f)); | ||
} | ||
@@ -224,20 +218,21 @@ updateResultMap(tasks, result) { | ||
* clean tasks | ||
* try to clean cache if needed | ||
* try to remove failed result, remove outdated cache if needed | ||
*/ cleanupTasks() { | ||
// no doing tasks, but the que is not empty, aka, there is some unresolved tasks | ||
if (this.taskQueue.length && !this.pendingTasks.length && !this.doingTasks.length) { | ||
this.checkAllTasks(new Error("not found")); | ||
this.taskQueue = []; | ||
} | ||
this.cleanCacheIfNeeded(); | ||
// has validity or retry flag and no taskQueue | ||
if ((this.invalidAfter || this.retryWhenFailed) && !this.taskQueue.length) { | ||
const now = Date.now(); | ||
this.doneTaskMap = this.doneTaskMap.filter((item)=>{ | ||
if (this.invalidAfter) return now - item.time <= this.invalidAfter; | ||
if (this.retryWhenFailed) return !(item.value instanceof Error); | ||
return true; | ||
}); | ||
} | ||
// has unresolved tasks, unable to cleanup task | ||
if (this.taskQueue.length) return; | ||
// no need to remove outdated or failed tasks | ||
if (!this.invalidAfter && !this.retryWhenFailed) return; | ||
const now = Date.now(); | ||
this.doneTaskMap = this.doneTaskMap.filter((item)=>{ | ||
if (this.retryWhenFailed && item.value instanceof Error) return false; | ||
if (this.invalidAfter) return now - item.time <= this.invalidAfter; | ||
return true; | ||
}); | ||
} | ||
static wrapError(e) { | ||
/** | ||
* wrap error info, if it's not instanceof Error, wrap it with Error | ||
* @returns Error instance | ||
*/ static wrapError(e) { | ||
if (e instanceof Error) return e; | ||
@@ -264,5 +259,5 @@ const newError = new Error("task failed"); | ||
* @returns | ||
*/ static async wrapPromise(promise) { | ||
*/ static async runTaskExecutor(executor, ...args) { | ||
try { | ||
const result = await promise; | ||
const result = await executor(...args); | ||
return { | ||
@@ -275,6 +270,44 @@ status: "fulfilled", | ||
status: "rejected", | ||
reason: error | ||
reason: $d415641d0cfd8c85$export$2e2bcd8739ae039.wrapError(error) | ||
}; | ||
} | ||
} | ||
/** | ||
* wrap do task to a batch version | ||
* @param doTask action to do single task | ||
* @returns batch version to do multi tasks | ||
*/ static wrapDoTask(doTask) { | ||
return async function(tasks) { | ||
const results = await Promise.all(tasks.map((t)=>$d415641d0cfd8c85$export$2e2bcd8739ae039.runTaskExecutor(doTask, t))); | ||
return tasks.map((t, idx)=>{ | ||
const result = results[idx]; | ||
return [ | ||
t, | ||
result.status === "fulfilled" ? result.value : result.reason | ||
]; | ||
}); | ||
}; | ||
} | ||
/** | ||
* check whether the given values are equal (with deep comparison) | ||
*/ static isEqual(a, b) { | ||
if (a === b) return true; | ||
const typeA = typeof a; | ||
const typeB = typeof b; | ||
if (typeA !== typeB) return false; | ||
// @ts-ignore | ||
// for nan | ||
if (typeA === "number" && isNaN(a) && isNaN(b)) return true; | ||
// none object type, aka primitive types, are checked by the first line | ||
if (typeA !== "object") return false; | ||
// if one of them is regexp, check via regexp literal | ||
if (a instanceof RegExp || b instanceof RegExp) return String(a) === String(b); | ||
// only one is array | ||
if (Array.isArray(a) !== Array.isArray(b)) return false; | ||
// @ts-ignore | ||
if (Object.keys(a).length !== Object.keys(b).length) return false; | ||
// @ts-ignore | ||
if (Object.keys(a).some((k)=>!$d415641d0cfd8c85$export$2e2bcd8739ae039.isEqual(a[k], b[k]))) return false; | ||
return true; | ||
} | ||
} | ||
@@ -281,0 +314,0 @@ |
@@ -16,5 +16,6 @@ function $parcel$defineInteropFlag(a) { | ||
*/ static defaultOptions = { | ||
isSameTask: (a, b)=>a === b, | ||
isSameTask: $f5bfd4ce37214f4f$export$2e2bcd8739ae039.isEqual, | ||
taskExecStrategy: "parallel", | ||
maxWaitingGap: 50, | ||
invalidAfter: 1000, | ||
taskWaitingStrategy: "debounce", | ||
@@ -35,6 +36,5 @@ retryWhenFailed: true | ||
this.maxWaitingGap = userOptions.maxWaitingGap; | ||
// @ts-ignore | ||
this.taskWaitingStrategy = userOptions.taskWaitingStrategy; | ||
this.batchDoTasks = userOptions.batchDoTasks; | ||
// @ts-ignore | ||
if (!userOptions.batchDoTasks && !userOptions.doTask) throw new Error("one of batchDoTasks / doTask must be specified"); | ||
this.batchDoTasks = userOptions.batchDoTasks || $f5bfd4ce37214f4f$export$2e2bcd8739ae039.wrapDoTask(userOptions.doTask); | ||
this.taskExecStrategy = userOptions.taskExecStrategy; | ||
@@ -49,4 +49,4 @@ this.retryWhenFailed = userOptions.retryWhenFailed; | ||
const result = this.tryGetTaskResult(tasks); | ||
if (result instanceof Error) throw result; | ||
return result; | ||
if (result instanceof Error) return Promise.reject(result); | ||
return Promise.resolve(result); | ||
} catch (error) { | ||
@@ -88,5 +88,9 @@ // not found | ||
]; | ||
// 去除掉正在等待的、正在处理的以及已经成功的 | ||
// remove duplicated tasks in itself | ||
myTasks = myTasks.filter((task, idx)=>idx === myTasks.findIndex((t)=>this.isSameTask(t, task))); | ||
// remove pending tasks | ||
if (this.pendingTasks.length) myTasks = myTasks.filter((f)=>!this.hasTask(this.pendingTasks, f)); | ||
// remove doing tasks | ||
if (myTasks.length && this.doingTasks.length) myTasks = myTasks.filter((f)=>!this.hasTask(this.doingTasks, f)); | ||
// remove done tasks | ||
if (myTasks.length) myTasks = myTasks.filter((f)=>!this.getTaskResult(f)); | ||
@@ -107,9 +111,9 @@ if (!myTasks.length) return; | ||
* if taskExecStrategy is parallel then do it immediately, | ||
* otherwise waiting util taskQueue is empty | ||
* otherwise waiting util doingTasks is empty | ||
*/ tryTodDoTasks() { | ||
// should exec in serial, and still has executing tasks | ||
if (this.taskExecStrategy === "serial" && this.taskQueue.length) { | ||
if (this.taskExecStrategy === "serial" && this.doingTasks.length) { | ||
clearTimeout(this.delayTimeoutId); | ||
// wait a moment then check again | ||
this.delayTimeoutId = setTimeout(this.tryTodDoTasks, 100); | ||
this.delayTimeoutId = setTimeout(this.tryTodDoTasks, 50); | ||
} else this.doTasks(); | ||
@@ -137,25 +141,17 @@ } | ||
else { | ||
try { | ||
const allResponse = await Promise.all(tasksGroup.map((taskList)=>$f5bfd4ce37214f4f$export$2e2bcd8739ae039.wrapPromise(this.batchDoTasks(taskList)))); | ||
allResponse.forEach((result, index)=>{ | ||
this.updateResultMap(tasksGroup[index], // @ts-ignore | ||
result.status === "rejected" ? $f5bfd4ce37214f4f$export$2e2bcd8739ae039.wrapError(result.reason) : result.value); | ||
}); | ||
} catch (error1) { | ||
this.updateResultMap(tasks, $f5bfd4ce37214f4f$export$2e2bcd8739ae039.wrapError(error1)); | ||
} | ||
const allResponse = await Promise.all(tasksGroup.map((taskList)=>$f5bfd4ce37214f4f$export$2e2bcd8739ae039.runTaskExecutor(this.batchDoTasks, taskList))); | ||
allResponse.forEach((result, index)=>{ | ||
this.updateResultMap(tasksGroup[index], result.status === "rejected" ? $f5bfd4ce37214f4f$export$2e2bcd8739ae039.wrapError(result.reason) : result.value); | ||
}); | ||
this.checkAllTasks(); | ||
this.removeDoneTasks(tasks); | ||
} | ||
// remove all unresolved tasks | ||
this.cleanupTasks(); | ||
} | ||
/** | ||
* 检查所有任务 | ||
* @param succeed 获取成功的结果对象 | ||
* @param failed 获取失败的 fileId 数组 | ||
*/ checkAllTasks(defaultResult) { | ||
* check all tasks, try to resolve | ||
*/ checkAllTasks() { | ||
this.taskQueue.forEach((taskItem)=>{ | ||
try { | ||
const result = this.tryGetTaskResult(taskItem.tasks, defaultResult); | ||
const result = this.tryGetTaskResult(taskItem.tasks); | ||
// eslint-disable-next-line no-param-reassign | ||
@@ -172,12 +168,14 @@ taskItem.isDone = true; | ||
} | ||
tryGetTaskResult(tasks, defaultResult) { | ||
/** | ||
* get result list of given tasks | ||
* throw error when not found(to make it easier to distinct from falsy results) | ||
* @param tasks tasks to check | ||
* @param defaultResult default result if not found | ||
*/ tryGetTaskResult(tasks) { | ||
// no cached data and no default result provided | ||
if (!this.doneTaskMap.length && !defaultResult) throw new Error("no done task"); | ||
if (!this.doneTaskMap.length) throw new Error("no done task"); | ||
if (Array.isArray(tasks)) { | ||
const result = []; | ||
return tasks.reduce((acc, task)=>{ | ||
const val = this.getTaskResult(task) || (defaultResult ? [ | ||
task, | ||
defaultResult | ||
] : false); | ||
const val = this.getTaskResult(task) || false; | ||
if (!val) throw new Error("not found"); | ||
@@ -188,6 +186,3 @@ acc.push(val); | ||
} | ||
const val = this.getTaskResult(tasks) || (defaultResult ? [ | ||
tasks, | ||
defaultResult | ||
] : false); | ||
const val = this.getTaskResult(tasks) || false; | ||
if (!val) throw new Error("not found"); | ||
@@ -202,3 +197,2 @@ return val[1]; | ||
]; | ||
return undefined; | ||
} | ||
@@ -209,3 +203,3 @@ hasTask(list, task) { | ||
removeDoneTasks(tasks) { | ||
this.doingTasks = this.doingTasks.filter((f)=>this.hasTask(tasks, f)); | ||
this.doingTasks = this.doingTasks.filter((f)=>!this.hasTask(tasks, f)); | ||
} | ||
@@ -235,20 +229,21 @@ updateResultMap(tasks, result) { | ||
* clean tasks | ||
* try to clean cache if needed | ||
* try to remove failed result, remove outdated cache if needed | ||
*/ cleanupTasks() { | ||
// no doing tasks, but the que is not empty, aka, there is some unresolved tasks | ||
if (this.taskQueue.length && !this.pendingTasks.length && !this.doingTasks.length) { | ||
this.checkAllTasks(new Error("not found")); | ||
this.taskQueue = []; | ||
} | ||
this.cleanCacheIfNeeded(); | ||
// has validity or retry flag and no taskQueue | ||
if ((this.invalidAfter || this.retryWhenFailed) && !this.taskQueue.length) { | ||
const now = Date.now(); | ||
this.doneTaskMap = this.doneTaskMap.filter((item)=>{ | ||
if (this.invalidAfter) return now - item.time <= this.invalidAfter; | ||
if (this.retryWhenFailed) return !(item.value instanceof Error); | ||
return true; | ||
}); | ||
} | ||
// has unresolved tasks, unable to cleanup task | ||
if (this.taskQueue.length) return; | ||
// no need to remove outdated or failed tasks | ||
if (!this.invalidAfter && !this.retryWhenFailed) return; | ||
const now = Date.now(); | ||
this.doneTaskMap = this.doneTaskMap.filter((item)=>{ | ||
if (this.retryWhenFailed && item.value instanceof Error) return false; | ||
if (this.invalidAfter) return now - item.time <= this.invalidAfter; | ||
return true; | ||
}); | ||
} | ||
static wrapError(e) { | ||
/** | ||
* wrap error info, if it's not instanceof Error, wrap it with Error | ||
* @returns Error instance | ||
*/ static wrapError(e) { | ||
if (e instanceof Error) return e; | ||
@@ -275,5 +270,5 @@ const newError = new Error("task failed"); | ||
* @returns | ||
*/ static async wrapPromise(promise) { | ||
*/ static async runTaskExecutor(executor, ...args) { | ||
try { | ||
const result = await promise; | ||
const result = await executor(...args); | ||
return { | ||
@@ -286,8 +281,46 @@ status: "fulfilled", | ||
status: "rejected", | ||
reason: error | ||
reason: $f5bfd4ce37214f4f$export$2e2bcd8739ae039.wrapError(error) | ||
}; | ||
} | ||
} | ||
/** | ||
* wrap do task to a batch version | ||
* @param doTask action to do single task | ||
* @returns batch version to do multi tasks | ||
*/ static wrapDoTask(doTask) { | ||
return async function(tasks) { | ||
const results = await Promise.all(tasks.map((t)=>$f5bfd4ce37214f4f$export$2e2bcd8739ae039.runTaskExecutor(doTask, t))); | ||
return tasks.map((t, idx)=>{ | ||
const result = results[idx]; | ||
return [ | ||
t, | ||
result.status === "fulfilled" ? result.value : result.reason | ||
]; | ||
}); | ||
}; | ||
} | ||
/** | ||
* check whether the given values are equal (with deep comparison) | ||
*/ static isEqual(a, b) { | ||
if (a === b) return true; | ||
const typeA = typeof a; | ||
const typeB = typeof b; | ||
if (typeA !== typeB) return false; | ||
// @ts-ignore | ||
// for nan | ||
if (typeA === "number" && isNaN(a) && isNaN(b)) return true; | ||
// none object type, aka primitive types, are checked by the first line | ||
if (typeA !== "object") return false; | ||
// if one of them is regexp, check via regexp literal | ||
if (a instanceof RegExp || b instanceof RegExp) return String(a) === String(b); | ||
// only one is array | ||
if (Array.isArray(a) !== Array.isArray(b)) return false; | ||
// @ts-ignore | ||
if (Object.keys(a).length !== Object.keys(b).length) return false; | ||
// @ts-ignore | ||
if (Object.keys(a).some((k)=>!$f5bfd4ce37214f4f$export$2e2bcd8739ae039.isEqual(a[k], b[k]))) return false; | ||
return true; | ||
} | ||
} | ||
@@ -0,13 +1,61 @@ | ||
export type ITaskExecStrategy = 'parallel' | 'serial'; | ||
export type ITaskWaitingStrategy = 'throttle' | 'debounce'; | ||
export default class AsyncTask<Task, Result> { | ||
constructor(options: { | ||
/** | ||
* max batch tasks count when dispatching | ||
*/ | ||
maxBatchCount?: number; | ||
batchDoTasks: (tasks: Task[]) => Promise<Array<[Task, Result | Error]>>; | ||
taskExecStrategy?: 'parallel' | 'serial'; | ||
/** | ||
* action to do batch tasks | ||
* one of batchDoTasks/doTask must be specified, batchDoTasks will take priority | ||
*/ | ||
batchDoTasks?: (tasks: Task[]) => Promise<Array<[Task, Result | Error]>> | Array<[Task, Result | Error]>; | ||
/** | ||
* action to do single task | ||
* one of batchDoTasks/doTask must be specified, batchDoTasks will take priority | ||
*/ | ||
doTask?: (task: Task) => Promise<Result> | Result; | ||
/** | ||
* do batch tasks executing strategy, default to parallel | ||
*/ | ||
taskExecStrategy?: ITaskExecStrategy; | ||
/** | ||
* max waiting time(in milliseconds) for combined tasks, default to 50 | ||
*/ | ||
maxWaitingGap?: number; | ||
/** | ||
* task result caching duration(in milliseconds), default to 1s | ||
* >`undefined` or `0` for unlimited | ||
* >set to minimum value `1` to disable caching | ||
* | ||
* *cache is lazy cleaned after invalid* | ||
*/ | ||
invalidAfter?: number; | ||
/** | ||
* retry failed tasks next time after failing, default true | ||
*/ | ||
retryWhenFailed?: boolean; | ||
/** | ||
* task waiting stragy, 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?: ITaskWaitingStrategy; | ||
/** | ||
* check whether two tasks are identified the same | ||
*/ | ||
isSameTask?: (a: Task, b: Task) => boolean; | ||
}); | ||
/** | ||
* execute task, get task result in promise | ||
*/ | ||
dispatch(task: Task): Promise<Result>; | ||
dispatch(tasks: Task[]): Promise<[[Task, Result | Error]]>; | ||
/** | ||
* execute tasks, get response in tuple of task and result/error | ||
*/ | ||
dispatch<T extends readonly Task[] | []>(tasks: T): Promise<{ | ||
[k in keyof T]: [Task, Result | Error]; | ||
}>; | ||
/** | ||
* clean cached task result | ||
@@ -17,4 +65,8 @@ * this may not exec immediately, it will take effect after all tasks are done | ||
cleanCache(): void; | ||
static wrapError(e: any): Error; | ||
/** | ||
* wrap error info, if it's not instanceof Error, wrap it with Error | ||
* @returns Error instance | ||
*/ | ||
static wrapError(e: unknown): Error; | ||
/** | ||
* split array to chunks with specified size | ||
@@ -32,11 +84,19 @@ * @param arr array of fileIds | ||
*/ | ||
static wrapPromise<T>(promise: Promise<T>): Promise<{ | ||
status: string; | ||
value: Awaited<T>; | ||
reason?: undefined; | ||
static runTaskExecutor<A extends Array<unknown>, F extends ((...args: A) => unknown)>(executor: F, ...args: A): Promise<{ | ||
status: 'fulfilled'; | ||
value: Awaited<ReturnType<F>>; | ||
} | { | ||
status: string; | ||
reason: unknown; | ||
value?: undefined; | ||
status: 'rejected'; | ||
reason: Error; | ||
}>; | ||
/** | ||
* wrap do task to a batch version | ||
* @param doTask action to do single task | ||
* @returns batch version to do multi tasks | ||
*/ | ||
static wrapDoTask<T, R>(doTask: (t: T) => Promise<R> | R): (tasks: T[]) => Promise<Array<[T, R | Error]>>; | ||
/** | ||
* check whether the given values are equal (with deep comparison) | ||
*/ | ||
static isEqual(a: unknown, b: unknown): boolean; | ||
} |
{ | ||
"name": "async-task-schedule", | ||
"version": "0.0.2", | ||
"version": "0.0.3", | ||
"description": "schedule async tasks", | ||
@@ -12,4 +12,4 @@ "main": "dist/index.js", | ||
"clean": "rimraf dist", | ||
"test": "jest", | ||
"prepublish": "yarn test && yarn build" | ||
"test": "jest --coverage", | ||
"release": "yarn test && yarn build && yarn publish" | ||
}, | ||
@@ -16,0 +16,0 @@ "keywords": [ |
@@ -24,3 +24,3 @@ # async-task-schedule | ||
* combine tasks' requests in same time and do all together | ||
* can prevent massive requests at same time, make them executing one group by one group | ||
* can prevent massive requests at same time, make them execute one group by one group | ||
* cache result and can specify validity | ||
@@ -76,6 +76,12 @@ | ||
* | ||
* batchDoTasks should receive multi tasks, and return tuple of task and response or error in array | ||
* batchDoTasks should receive multitasks, and return tuple of task and response or error in array | ||
* one of batchDoTasks/doTask must be specified, batchDoTasks will take priority | ||
*/ | ||
private batchDoTasks: (tasks: Task[]) => Promise<Array<[Task, Result | Error ]>> | ||
/** | ||
* action to do single task | ||
* one of batchDoTasks/doTask must be specified, batchDoTasks will take priority | ||
*/ | ||
doTask?: ((task: Task) => Promise<Result>) | ||
@@ -96,3 +102,3 @@ /** | ||
/** | ||
* do batch tasks executing strategy, default parallel | ||
* batch tasks executing strategy, default parallel | ||
* only works if maxBatchCount is specified and tasks more than maxBatchCount are executed | ||
@@ -103,3 +109,3 @@ * | ||
* if serial specified, when tasks are executing, new comings will wait for them to complete | ||
* it's very useful to cool down task requests | ||
* it's especially useful to cool down task requests | ||
*/ | ||
@@ -124,2 +130,3 @@ private taskExecStrategy: 'parallel' | 'serial' | ||
* undefined or 0 for unlimited | ||
* cache is lazy cleaned after invalid | ||
*/ | ||
@@ -136,5 +143,5 @@ private invalidAfter?: number | ||
### dispatch(tasks: Task[]):Promise<Array<[Task, Result | Error]>> | ||
dispatch multi tasks at a time, will get tuple of task and response in array | ||
dispatch multitasks at a time, will get tuple of task and response in array | ||
this method won't throw any error, it will fulfilled even partially failed, you can check whether its success by `tuple[1] instanceof Error` | ||
this method won't throw any error, it will fulfil even partially failed, you can check whether its success by `tuple[1] instanceof Error` | ||
@@ -155,3 +162,3 @@ ### dispatch(tasks: Task):Promise<Result> | ||
#### example 1 | ||
suppose we have a method `getUsers` defined as follow: | ||
suppose we have a method `getUsers` defined as follows: | ||
@@ -187,5 +194,5 @@ ```ts | ||
#### example 2 | ||
if the request parameter is a object, then we should provide a `isSameTask` to identify unique task | ||
if the request parameter is an object, then we should provide a `isSameTask` to identify unique task | ||
suppose we have a method `decodeGeoPoints` defined as follow: | ||
suppose we have a method `decodeGeoPoints` defined as follows: | ||
@@ -196,3 +203,4 @@ ```ts | ||
then we can integrate as follow | ||
then we can integrate as follows: | ||
```ts | ||
@@ -217,3 +225,3 @@ async function batchDecodeGeoPoints(points: Array<{lat: number, long: number}>): Promise<Array<[{lat: number, long: number}, {point: {lat: number, long: number}, title: string, description: string, country: string}]>> { | ||
// request combine won't works when using await | ||
// request combine won't work when using await | ||
const result1 = await decodePointsAsyncTask.dispatch([{lat: 23.232, long: 43.121}, {lat: 33.232, long: 11.1023}]) | ||
@@ -224,3 +232,3 @@ const result2 = await decodePointsAsyncTask.dispatch([{lat: 23.232, long: 43.121}, {lat: 33.232, long: 44.2478}]).then(console.log) | ||
### how to cool down massive requests at same time | ||
### 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 | ||
@@ -227,0 +235,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
185346
17
1380
234
1