{"id":17109940,"url":"https://github.com/oe/async-task-schedule","last_synced_at":"2026-02-28T13:33:34.127Z","repository":{"id":59914009,"uuid":"539982641","full_name":"oe/async-task-schedule","owner":"oe","description":"schedule  async tasks, remove redundant with cache support","archived":false,"fork":false,"pushed_at":"2024-11-20T16:59:55.000Z","size":160,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-13T23:03:24.926Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oe.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-09-22T12:57:34.000Z","updated_at":"2025-07-22T08:08:57.000Z","dependencies_parsed_at":"2024-11-20T17:24:13.695Z","dependency_job_id":"c1710d9d-7bdb-4029-bea4-bc5676f4181b","html_url":"https://github.com/oe/async-task-schedule","commit_stats":{"total_commits":25,"total_committers":4,"mean_commits":6.25,"dds":0.4,"last_synced_commit":"3ed7048a90c808e85f02e69ff3a1af23d040a33e"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/oe/async-task-schedule","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oe%2Fasync-task-schedule","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oe%2Fasync-task-schedule/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oe%2Fasync-task-schedule/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oe%2Fasync-task-schedule/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oe","download_url":"https://codeload.github.com/oe/async-task-schedule/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oe%2Fasync-task-schedule/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29935368,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-28T13:16:57.922Z","status":"ssl_error","status_checked_at":"2026-02-28T13:11:15.149Z","response_time":90,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-14T16:24:53.382Z","updated_at":"2026-02-28T13:33:34.093Z","avatar_url":"https://github.com/oe.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# async-task-schedule\n\n\n\u003cdiv align=\"center\"\u003e\n  \u003ca href=\"https://github.com/oe/async-task-schedule/actions\"\u003e\n    \u003cimg src=\"https://github.com/oe/async-task-schedule/actions/workflows/main.yml/badge.svg\" alt=\"github actions\"\u003e\n  \u003c/a\u003e\n  \u003cimg src=\"https://img.shields.io/badge/coverage-100%25-brightgreen?logo=codecov\" alt=\"use it with confident\"\u003e\n  \u003ca href=\"#readme\"\u003e\n    \u003cimg src=\"https://badgen.net/badge/Built%20With/TypeScript/blue\" alt=\"code with typescript\" height=\"20\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"#readme\"\u003e\n    \u003cimg src=\"https://badge.fury.io/js/async-task-schedule.svg\" alt=\"npm version\" height=\"20\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/async-task-schedule\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/dm/async-task-schedule.svg\" alt=\"npm downloads\" height=\"20\"\u003e\n  \u003c/a\u003e\n\u003c/div\u003e\n\nschedule async tasks in order\n\n## Features\n* remove duplicated tasks' requests\n* combine tasks' requests in same time and do all together\n* can prevent massive requests at same time, make them execute one group by one group\n* cache result and can specify validity\n\n\n## Install\n```sh\nyarn add async-task-schedule\n# or with npm\nnpm install async-task-schedule -S\n```\n\n## Usage\n\n```ts\nimport TaskSchedule from 'async-task-schedule'\nlet count = 0\nconst taskSchedule = new TaskSchedule({\n    doTask: async (name: string) =\u003e {\n        count += 1\n      return `${name}${count}`\n    },\n    // or use this will do the same\n    // batchDoTasks: async (names: string[]) =\u003e {\n    //   count += 1\n    //   return names.map((n) =\u003e `${n}${count}`)\n    // },\n})\n\ntaskSchedule.dispatch(['a', 'b']).then(console.log)\ntaskSchedule.dispatch(['b', 'c']).then(console.log)\ntaskSchedule.dispatch(['d', 'c']).then(console.log)\ntaskSchedule.dispatch('c').then(console.log)\n// batchDoTasks will be only called once\n\n```\n**NOTICE**: in following example, tasks won't combine\n```ts\n// batchDoTasks will be executed 3 times due to javascript language features\nconst result1 = await taskSchedule.dispatch(['a', 'b'])\nconst result2 = await taskSchedule.dispatch(['b', 'c'])\nconst result3 = await taskSchedule.dispatch(['d', 'c'])\nconst result4 = await taskSchedule.dispatch('c')\n```\n\n\n## API\n\n### constructor(options: ITaskScheduleOptions)\n\noptions define:\n\n```ts\n// `Task` for single task's parameters\n// `Result` for single task's response\ninterface ITaskScheduleOptions\u003cTask, Result\u003e {\n  /**\n   * action to do batch tasks, can be async or sync function\n   *  Task: single task request info\n   *  Result: single task success response\n   * \n   * batchDoTasks should receive multitasks, and return result or error in order\n   * one of batchDoTasks/doTask must be specified, batchDoTasks will take priority\n   */\n  batchDoTasks: (tasks: Task[]) =\u003e Promise\u003cArray\u003cResult | Error\u003e\u003e | Array\u003cResult | Error\u003e\n\n  /**\n   * action to do single task, can be async or sync function\n   *  one of batchDoTasks/doTask must be specified, batchDoTasks will take priority\n   */\n  doTask?: (task: Task) =\u003e Promise\u003cResult\u003e | Result\n\n  /**\n   * check whether two tasks are equal\n   *  it helps to avoid duplicated tasks\n   *  default: AsyncTask.isEqual (deep equal)\n   */\n  isSameTask?: (a: Task, b: Task) =\u003e boolean\n\n  /**\n   * max task count for batchDoTasks, default unlimited\n   *  undefined or 0 for unlimited\n   */\n  maxBatchCount?: number\n\n  /**\n   * batch tasks executing strategy, default parallel\n   *  only works if maxBatchCount is specified and tasks more than maxBatchCount are executed\n   *  \n   * parallel: split all tasks into a list stride by maxBatchCount, exec them at the same time\n   * serial: split all tasks into a list stride by maxBatchCount, exec theme one group by one group\n   *    if serial specified, when tasks are executing, new comings will wait for them to complete\n   *    it's especially useful to cool down task requests\n   */\n  taskExecStrategy: 'parallel' | 'serial'\n\n  /**\n   * task waiting strategy, default to debounce\n   *  throttle: tasks will combined and dispatch every `maxWaitingGap`\n   *  debounce: tasks will combined and dispatch util no more tasks in next `maxWaitingGap`\n   */\n  taskWaitingStrategy: 'throttle' | 'debounce'\n\n  /**\n   * task waiting time in milliseconds, default 50ms\n   *     differently according to taskWaitingStrategy\n   */\n  maxWaitingGap: number\n\n\n  /**\n   * task result caching duration(in milliseconds), default to 1000ms (1s)\n   * \u003e `undefined` or `0` for unlimited  \n   * \u003e set to minimum value `1` to disable caching  \n   * \u003e `function` to specified specified each task's validity\n   * \n   * *cache is lazy cleaned after invalid*\n   */\n  invalidAfter?: number | ((task: Task, result: Result | Error) =\u003e number)\n\n  /**\n   * retry failed tasks next time after failing, default true\n   */\n  retryWhenFailed?: boolean\n}\n```\n\nexample:\n```ts\nimport TaskSchedule from 'async-task-schedule'\n\nconst taskSchedule = new TaskSchedule({\n  doTask(n) { \n    console.log(`do task with ${n}`)\n    return n * n\n  },\n  invalidAfter: 0\n})\n\nconst result = await taskSchedule.dispatch([1,2,3,1,2])\n// get first result\nconst resultOf1 = result[0] // 1\n// doTask won't be called\nconst result11 = await taskSchedule.dispatch(1) // 1\n\n// clean all cached result\ntaskSchedule.cleanCache()\n// doTask will be call again\nconst result12 = await taskSchedule.dispatch(1) // 1\n\n```\n\n### dispatch(tasks: Task[]):Promise\u003cArray\u003cResult | Error\u003e\u003e\ndispatch multitasks at a time, will get response with corresponding order of `tasks`\nthis method won't throw any error, it will fulfil even partially failed, you can check whether its success by `response instanceof Error`\n\n```ts\nimport TaskSchedule from 'async-task-schedule'\n\nconst taskSchedule = new TaskSchedule({\n  doTask(n) { \n    console.log(`do task with ${n}`)\n    if (n % 2) throw new Error(`${n} is unsupported`)\n    return n * n\n  },\n  invalidAfter: 0\n})\n\nconst result = await taskSchedule.dispatch([1,2,3,1,2])\n// get first result\nconst resultOf1 = result[0] // 1\n// second result is error\nconst isError = result[1] instanceof Error // error object\n\n\ntry {\n  // will throw an error\n  const result2 = await taskSchedule.dispatch(2) // 1\n} catch(error) {\n  console.warn(error)\n}\n```\n\n\n### dispatch(tasks: Task):Promise\u003cResult\u003e\ndispatch a task, will get response if success, or throw an error\n\n```ts\nimport TaskSchedule from 'async-task-schedule'\n\nconst taskSchedule = new TaskSchedule({\n  doTask(n) { \n    console.log(`do task with ${n}`)\n    if (n % 2) throw new Error(`${n} is unsupported`)\n    return n * n\n  },\n  invalidAfter: 0\n})\n\nconst result1 = await taskSchedule.dispatch(1) // 1\ntry {\n  const result2 = await taskSchedule.dispatch(2),\n} catch(error) {\n  console.warn(error)\n}\n```\n\n### cleanCache\nclean cached tasks' result, so older task will trigger new request and get fresh response.\n\nattention: this action may not exec immediately, it will take effect after all tasks are done\n\n```ts\nimport TaskSchedule from 'async-task-schedule'\n\nconst taskSchedule = new TaskSchedule({\n  doTask(n) { \n    console.log(`do task with ${n}`)\n    return n * n\n  },\n  invalidAfter: 0\n})\n\nawait Promise.all([\n  taskSchedule.dispatch([1, 2, 3, 1, 2]),\n  taskSchedule.dispatch([1, 9, 10, 12, 22]),\n])\n// clean all cached result\ntaskSchedule.cleanCache()\n// task will execute again\nconst result = await taskSchedule.dispatch(1),\n\n```\n\n## utils methods\nthere are some utils method as static members of `async-task-schedule`\n\n\n### isEqual(a: unknown, b: unknown): boolean\ncheck whether the given values are equal (with deep comparison)\n\n```ts\nimport TaskSchedule from 'async-task-schedule'\n\nTaskSchedule.isEqual(1, '1') // false\nTaskSchedule.isEqual('1', '1') // true\nTaskSchedule.isEqual(NaN, NaN) // true\nTaskSchedule.isEqual({a: 'a', b: 'b'}, {b: 'b', a: 'a'}) // true\nTaskSchedule.isEqual({a: 'a', b: 'b', c: {e: [1,2,3]}}, {b: 'b', c: {e: [1,2,3]}, a: 'a'}) // true\nTaskSchedule.isEqual({a: 'a', b: /acx/}, {b: new RegExp('acx'), a: 'a'}) // true\n```\nyou can use it to check whether two tasks are equal / find specified task\n\n\n## Receipts\n\n### how to integrate with existing code\nwhat you need to do is to wrap your existing task executing function into a new `batchDoTasks`\n\n#### example 1: cache `fetch`\nsuppose we use browser native `fetch` to send request, we can do so to make an improvement:\n\n```ts\n\nconst fetchSchedule = new TaskSchedule({\n  async doTask(cfg: {resource: string, options?: RequestInit}) {\n    return await fetch(cfg.resource, cfg.options)\n  },\n  // 0 for forever\n  // set a minimum number 1 can disable cache after 1 millisecond\n  invalidAfter(cfg, result) {\n    // cache get request for 3s\n    if (!cfg.options || !cfg.options.method || !cfg.options.method.toLowerCase() === 'get') {\n      // cache sys static config forever\n      if (/\\/sys\\/static-config$/.test(cfg.resource)) return 0\n      return 3000\n    }\n    // disable other types request\n    return 1\n  }\n})\n\nconst betterFetch = (resource: string, options?: RequestInit) =\u003e {\n  return fetchSchedule.dispatch({resource, options})\n}\n\n// than you can replace fetch with betterFetch\n```\n\nwith those codes above:\n1. you can remove redundant request(requests with same parameters at same time will be reduced to one, this may have some side effects)\n2. get request can be cached in a short time\n\n\n#### example 2: deal with `getUsers`\nsuppose we have a method `getUsers` defined as follows:\n\n```ts\n// support max 5 users at a time\ngetUsers(userIds: string[]) =\u003e Promise\u003c[{code: string, message: string, id?: string, name?: string, email?: string}]\u003e\n``` \n\nthen we can implement a batch version:\n```ts\nasync function batchGetUsers(userIds: string[]): Promise\u003cArray\u003c[string, {id: string, name: string, email: string}]\u003e\u003e {\n  // there is no need to try/catch, errors will be handled properly\n  const users = await getUsers(userIds)\n  // convert invalid users to error\n  return users.map(user =\u003e (user.code === 'failed' ? new Error(user.message) : user))\n}\n\nconst getUserSchedule = new TaskSchedule({\n  maxBatchCount: 5,\n  batchDoTasks: batchGetUsers,\n  // cache user info forever\n  invalidAfter: 0,\n})\n\nconst result = await Promise.all([\n  getUserSchedule.dispatch(['user1', 'user2', 'user3', 'user4', 'user5', 'user6']),\n  getUserSchedule.dispatch(['user3', 'user2'])\n  getUserSchedule.dispatch(['user6', 'user7', 'user8', 'user9', 'user10'])\n  getUserSchedule.dispatch(['user2', 'user6', 'user9'])\n])\n// only 2 requests will be sent via getUsers with userIds ['user1', 'user2', 'user3', 'user4', 'user5'] and ['user6', 'user7', 'user8', 'user9', 'user10']\n\n// request combine won't works when using await separately\nconst result1 = await getUserSchedule.dispatch(['user1', 'user2'])\nconst result2 = await getUserSchedule.dispatch(['user3', 'user2'])\n```\n\nIf you got a batch version function, you just need to make sure it throw an error when error occurred.\n\n\n### how to cool down massive requests at the same time\nby setting `taskExecStrategy` to `serial` and using smaller `maxBatchCount`(you can even set it to `1`), you can achieve this easily\n\n```ts\nconst taskSchedule = new TaskSchedule({\n  ...,\n  taskExecStrategy: 'serial',\n  maxBatchCount: 2,\n})\n```\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foe%2Fasync-task-schedule","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foe%2Fasync-task-schedule","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foe%2Fasync-task-schedule/lists"}