Skip to content

Events, promises and asynchronous operations simplified

Notifications You must be signed in to change notification settings

elemental-mind/deferium

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Deferium

Events, promises and asynchronous operations simplified! Through a few useful and composable primitives, Deferium gives you a toolbox to greatly simplify the handling and organisation of complex time-dependent and asynchronous processes.

By providing a suite of classes to extend native Promise functionality, Deferium allows developers to manage asynchronous tasks with greater control and clarity. Whether you're dealing with deferred execution, timeouts, progress tracking, or complex promise chains, Deferium offers a structured approach to make your asynchronous code more readable and maintainable by easing application of modern async/await syntax.

Concepts

Traits

Deferium exposes a few Trait classes that you can use or derive from or that you can compose together to make your asynchronous operations/classes more semantically understandable and concise. Deferium is heavily class based and targeted for developers seeking an object oriented approach to complex state management.

The core traits Deferium defines and aids with are the following:

  • Awaitability
  • Cancellability
  • Memory-Leakability
  • Streamability
  • Subscribability

By providing composable base classes that already contain a lot of logic around these mentioned semantic elements it's easy to compose more complex classes that are Awaitable, Cancellable and Streamable at the same time.

Name-Mapping

As most of the traits are pretty universal, it may be desired to specify more specific names for methods and properties of these traits. When talking about processes in general, for example you may not want to reject a process, but rather abort it.

Deferium allows you to do that, without losing the logic behind the base types through its NameMapped types.

Through an abstraction layer, these traits can still be handled naming-independently by Frameworks, but adopt the specific semantics of your use case.

To Name-Map a type, just use its NameMappedX equivalent, if provided. More information will follow later.

Project status

This project is in early alpha.

Installation

To get started with Deferium, install the package using npm:

npm install deferium

Or using yarn:

yarn add deferium

Usage

Awaitable Types

If you ever tried to derive from a Promise directly you will know that it's not easy. The Awaitable types solve that problem and add additional logic.

Awaitable is an easily derivable drop-in-replacement for Promise.

Use it standalone:

class Process
{
    public completion = new Awaitable();

    ...
    onCompletion()
    {
        this.completion.resolve();
    }
    ...
}

Or use it as a base class:

class Process extends Awaitable
{
    ...
    onCompletion()
    {
        this.resolve();
    }
    ...
}

const FindNeedleInHaystack = new Process()

await FindNeedleInHaystack;

if(!FindNeedleInHaystack.isResolved)    //This is one of the properties that automatically gets updated
    console.log("Still searching");

console.log("Medal of Honour");

Cancellable Types

In many asynchronous operations, there is a need to abort an ongoing task before it naturally completes. The Cancellable types in Deferium provide a structured way to handle such scenarios, allowing you to not only initiate cancellation but also to react to it within your asynchronous workflows.

A Cancellable type extends the basic Promise functionality with methods to cancel the operation, check if it has been cancelled, and specify a cancellation reason. This makes it easier to manage resources and avoid potential memory leaks by cleaning up after a task that is no longer needed.

Here's how you can use a Cancellable type:

class LongRunningProcess extends Cancellable {
    constructor() {
        super();
        // Start the process
    }

    // Method to initiate the cancellation
    abortProcess() {
        this.cancel("Process was aborted by the user.");
    }
}

// Usage
const process = new LongRunningProcess();

// At some point later, if you need to abort the process:
process.abortProcess();

// You can check if the process was cancelled:
if (process.isCancelled) {
    console.log(`Process cancelled: ${process.cancelReason}`);
}

The Cancellable type ensures that your asynchronous operations are robust and respect the lifecycle of your application, preventing unintended side effects when operations are no longer relevant.

Remember to handle the cancellation in your asynchronous tasks through an override of the respective functions to properly free up resources and avoid executing further logic after the cancellation has been requested.

For more advanced use cases, you can combine Cancellable with other traits provided by Deferium to create complex types that are both awaitable and cancellable, among other things.

Streamable Types

Streamable types in Deferium provide a way to handle a sequence of asynchronous events or data chunks over time, similar to how streams work in many programming environments. These types are particularly useful when dealing with data that is not available all at once, allowing you to process each piece of data as it arrives.

A Streamable type typically includes methods to push data into the stream, subscribe to data events, and handle the end of the stream or errors that may occur during data processing.

Here's a basic example of how a Streamable type might be used:

class DataStreamer extends Streamable<string> {
    constructor() {
        super(); // Initialize the stream
    }

    pushData(data: string) {
        //...customizable logic
        this.emit(data);
    }

    endStream() {
        //...customizable logic
        this.close();
    }
}

async logStream()
{
    for await (const chunk of dataStreamer)
        console.log(stream);
}

// Usage
const dataStreamer = new DataStreamer();

logStream();

// Synchronous as well as asynchronous emissions to the stream are handled without losses
dataStreamer.pushData("First");
dataStreamer.pushData("Second");
await Delay.for(1000);
dataStreamer.pushData("Third");

dataStreamer.endStream();

The Streamable type allows you to build responsive and efficient data processing mechanisms that can handle real-time data, streams of API responses, or any other form of sequential data.

Subscribable Types

Subscribable types in Deferium provide a pattern for creating objects that other parts of your application can listen to for events or changes. This pattern is commonly used in event-driven programming and can help decouple the components of your system by allowing them to communicate through well-defined events.

A Subscribable type typically includes methods to subscribe to events, unsubscribe from events, and emit events to notify all current subscribers. This pattern is useful for creating custom events, implementing observer patterns, or simply for allowing parts of your application to react to changes in state or other significant occurrences.

Here's a basic example of how a Subscribable type might be used:

class EventHub
{
    ...
    public onMessage = new Subscribable();
    ...
}

// Usage
const dispatcher = new EventHub();

// Function to handle events
function eventHandler(data) {
    console.log(`Event received with data: ${data}`);
}

dispatcher.onMessage.subscribe(eventHandler);
dispatcher.onMessage.emit('Hello, Subscribers!');

// Unsubscribe from the 'customEvent' event
dispatcher.onMessage.unsubscribe(eventHandler);

The Subscribable type is a utility type for managing and coordinating events within your application.

The Subscribable type can of course also be derived from to have more customized subscriber management etc. or name mapped methods and members.

Memory-Leakable Types

Memory-Leakable Types The MemoryLeakable type in Deferium provides a unified interface to build disposable objects that need explicit cleanup to avoid memory leaks. This is useful for event handler subscriptions, database connections, network sockets, file handles, etc. that can leak if not properly closed.

A MemoryLeakable type has a dispose() method that should be called to clean up any resources when the object is no longer needed. It also exposes isDisposed to check if disposal has occurred.

Here is an example:

class DatabaseConnection extends MemoryLeakable {
  constructor() {
    super();
    this.handle = db.open(...)
  }
}

const db = new DatabaseConnection();

// use database 

db.dispose(); // avoid memory leak

The MemoryLeakable type ensures you build classes that cleanly free resources, avoiding hard-to-debug memory leaks. It enforces that disposal logic is implemented.

For advanced cases, you can combine with other Deferium types like Awaitable to make classes awaitable and disposable.

More coming soon...

Contributing

Contributions are welcome! If you have a feature request, bug report, or a pull request, please feel free to contribute to the project.

License

Deferium is open-sourced software licensed under the MIT license.

About

Events, promises and asynchronous operations simplified

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published