1.0.1 • Published 3 years ago

@matvp91/promise-scheduler v1.0.1

Weekly downloads
-
License
MIT
Repository
github
Last release
3 years ago

promise-scheduler

Run async code in a synchronous order by scheduling promises, with the possibility to cancel pending or active tasks. Optimized for the browser environment, less then 1KB in file size.

I came across a situation where I needed more fine-grained control over a sequence of async operations, with the added requirement that a single operational task should be abortable. Meaning that the task could abort earlier on in its execution in order for the next task to run. Think of a fetch call with an AbortController that makes the Promise resolve earlier due to the fact that the signal is cancelled along the way, but then in a sequence of multiple fetch calls.

A lot of the existing solutions do not take abortable operations into account, and they do too much for my liking (such as allowing concurrency). This is a lightweight yet limited implementation of a scheduler able to execute async code one after another.

Install

npm install @matpv91/promise-scheduler

API

import Scheduler from '@matvp91/promise-scheduler';

const scheduler = new Scheduler();

function createTask() {
  // Dummy function to define performing work over time.
  const work = () => new Promise(resolve => setTimeout(resolve, 500));

  return async (throwIfCanceled) => {
    await work();
    throwIfCanceled(); // Checks whether we can continue or discard the callstack below.
    await work();
    throwIfCanceled();
  };
}

// Schedule a task.
scheduler.scheduleCallback(createTask());

// Schedule a task with notifiers.
scheduler.scheduleCallback(createTask(), {
  onError: (error) => {}, // The task causes an error.
  onAborted: () => {}, // The task got aborted working.
});

// Pending (future) tasks are cancelled, and if a task
// is actively working, it gets aborted.
scheduler.flushWork();

// Returns a promise when no more work is pending.
scheduler.waitForIdle(): Promise

Lifecycle example

The example below illustrates a minimalistic Instance class with a load, unload and destroy lifecycle. The fact that the methods are async poses no risk of an unexpected side effect or race condition due to the fact that they're handled by a scheduler taking care of the order of execution.

class Instance {
  load() {
    this.unload();
    scheduler.scheduleCallback(/* an async, abortable task with plenty of work */);
  }

  unload() {
    scheduler.flushWork();
    scheduler.scheduleCallback(/* an async task for cleanup */);
  }

  async destroy() {
    this.unload();
    await scheduler.waitForIdle(); // Ensures no pending work is left.
  }
}