npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

async-task-schedule

v1.0.1

Published

schedule async tasks

Downloads

144

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

yarn add async-task-schedule
# or with npm
npm install async-task-schedule -S

Usage

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

// 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:

// `Task` for single task's parameters
// `Result` for single task's response
interface ITaskScheduleOptions<Task, Result> {
  /**
   * 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<Result | Error>> | Array<Result | Error>

  /**
   * 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> | 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 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: '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:

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<Array<Result | Error>>

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

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

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

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)

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:


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:

// 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:

async function batchGetUsers(userIds: string[]): Promise<Array<[string, {id: string, name: string, email: string}]>> {
  // 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

const taskSchedule = new TaskSchedule({
  ...,
  taskExecStrategy: 'serial',
  maxBatchCount: 2,
})