Skip to content

Constructor ​

Create a new FuturableTask instance.

Syntax ​

typescript
new FuturableTask<T>(
  executor: FuturableExecutor<T>,
  externalSignal?: AbortSignal
)

Parameters ​

executor ​

A function that defines the task's computation. Unlike Futurable, this function is not executed until .run() is called.

typescript
type FuturableExecutor<T> = (
  resolve: (value: T | FuturableLike<T> | PromiseLike<T>) => void,
  reject: (reason?: any) => void,
  utils: FuturableUtils<T>
) => void

The executor receives three parameters:

  • resolve: Function to fulfill the task with a value
  • reject: Function to reject the task with a reason
  • utils: Object containing utility methods and the abort signal

externalSignal (optional) ​

An external AbortSignal to control the task's cancellation. If provided, the task will be cancelled when this signal is aborted.

Return Value ​

A new FuturableTask<T> instance.

Description ​

The FuturableTask constructor creates a lazy async computation. Unlike Futurable, creating a FuturableTask does not execute the executor function. Execution only happens when you call .run().

Key Characteristics ​

  1. Lazy Evaluation: The executor is not called until .run() is invoked
  2. Reusable: Can be run multiple times, each execution is independent
  3. Cancellable: Can be cancelled before or during execution
  4. Composable: Can be transformed and chained before execution

Examples ​

Basic Usage ​

typescript
import { FuturableTask } from '@ndriadev/futurable';

// Creating the task doesn't execute it
const task = new FuturableTask((resolve, reject) => {
  console.log('Executing!');
  setTimeout(() => resolve('Done!'), 1000);
});
// Nothing logged yet

// Execute when ready
const result = await task.run(); // Logs: "Executing!"
console.log(result); // "Done!"

With Cancellation Support ​

typescript
const task = new FuturableTask((resolve, reject, { signal, onCancel }) => {
  const timeoutId = setTimeout(() => {
    resolve('Completed');
  }, 5000);

  // Register cleanup
  onCancel(() => {
    console.log('Cleaning up...');
    clearTimeout(timeoutId);
  });

  // Or use signal directly
  signal.addEventListener('abort', () => {
    clearTimeout(timeoutId);
    reject(new Error('Cancelled'));
  });
});

// Cancel before running
task.cancel();
await task.run(); // Won't execute

// Or cancel during execution
const execution = task.run();
setTimeout(() => task.cancel(), 1000);

With External AbortSignal ​

typescript
const controller = new AbortController();

const task = new FuturableTask(
  (resolve, reject) => {
    setTimeout(() => resolve('Done'), 1000);
  },
  controller.signal // External signal
);

// Cancel via external controller
controller.abort();

Async Executor ​

typescript
const task = new FuturableTask(async (resolve, reject, { signal }) => {
  try {
    const response = await fetch('https://api.example.com/data', { signal });
    const data = await response.json();
    resolve(data);
  } catch (error) {
    reject(error);
  }
});

Using Utils Methods ​

typescript
const task = new FuturableTask(async (resolve, reject, utils) => {
  try {
    // Wait 1 second
    await utils.sleep(1000);

    // Make a fetch request (automatically cancelled if task is cancelled)
    const response = await utils.fetch('https://api.example.com/data');
    const data = await response.json();

    resolve(data);
  } catch (error) {
    reject(error);
  }
});

With Cleanup Logic ​

typescript
const task = new FuturableTask((resolve, reject, { onCancel }) => {
  const ws = new WebSocket('wss://example.com');

  ws.onmessage = (event) => {
    resolve(event.data);
  };

  ws.onerror = (error) => {
    reject(error);
  };

  // Cleanup is called on cancellation
  onCancel(() => {
    ws.close();
  });
});

// Cleanup runs when cancelled
task.cancel();

Alternative Constructors ​

FuturableTask.of() ​

Create a task from a function (recommended):

typescript
const task = FuturableTask.of(() => {
  return expensiveOperation();
});

// With async function
const asyncTask = FuturableTask.of(async () => {
  const res = await fetch('/api/data');
  return res.json();
});

FuturableTask.resolve() ​

Create an immediately successful task:

typescript
const task = FuturableTask.resolve(42);
await task.run(); // 42

FuturableTask.reject() ​

Create an immediately failed task:

typescript
const task = FuturableTask.reject(new Error('Failed'));
await task.run(); // Throws Error

FuturableTask.from() ​

Convert a Futurable to a FuturableTask:

typescript
const futurable = Futurable.fetch('/api/data');
const task = FuturableTask.from(futurable);

Common Patterns ​

Resource Management ​

typescript
function managedResource() {
  return new FuturableTask((resolve, reject, { onCancel }) => {
    const resource = allocateResource();

    // Register cleanup
    onCancel(() => {
      resource.cleanup();
    });

    resource.process()
      .then(resolve)
      .catch(reject);
  });
}

Multiple Async Operations ​

typescript
const task = new FuturableTask(async (resolve, reject, { signal }) => {
  try {
    const [users, posts] = await Promise.all([
      fetch('/api/users', { signal }).then(r => r.json()),
      fetch('/api/posts', { signal }).then(r => r.json())
    ]);

    resolve({ users, posts });
  } catch (error) {
    reject(error);
  }
});

Conditional Execution ​

typescript
const task = new FuturableTask((resolve, reject, { signal }) => {
  const interval = setInterval(() => {
    if (signal.aborted) {
      clearInterval(interval);
      reject(new Error('Cancelled'));
      return;
    }

    const condition = checkCondition();
    if (condition) {
      clearInterval(interval);
      resolve('Condition met!');
    }
  }, 100);
});

Progress Tracking ​

typescript
const task = new FuturableTask(async (resolve, reject, { signal }) => {
  const steps = [step1, step2, step3, step4];

  for (let i = 0; i < steps.length; i++) {
    if (signal.aborted) {
      reject(new Error('Cancelled'));
      return;
    }

    updateProgress((i + 1) / steps.length * 100);
    await steps[i]();
  }

  resolve('Complete');
});

Type Parameters ​

T ​

The type of value the task will resolve to.

typescript
// Resolve with string
const stringTask = new FuturableTask<string>((resolve) => {
  resolve('hello');
});

// Resolve with object
interface User {
  id: number;
  name: string;
}

const userTask = new FuturableTask<User>((resolve) => {
  resolve({ id: 1, name: 'John' });
});

// Resolve with void
const voidTask = new FuturableTask<void>((resolve) => {
  resolve();
});

Comparison with Futurable Constructor ​

FeatureFuturableTaskFuturable
ExecutionLazy (on .run())Eager (immediate)
ReusabilityMultiple runsSingle use
BaseIndependent classExtends Promise
Use caseComposition & reusePromise replacement
typescript
// Futurable: Executes immediately
const futurable = new Futurable((resolve) => {
  console.log('Executing now!'); // Logs immediately
  resolve(42);
});

// FuturableTask: Lazy execution
const task = new FuturableTask((resolve) => {
  console.log('Executing now!'); // Nothing logged
  resolve(42);
});
await task.run(); // Logs: "Executing now!"

Notes ​

  • The executor function is not called when the task is created
  • Execution only happens when .run() is called
  • Each call to .run() creates a new independent execution
  • The signal parameter in utils is always available
  • Multiple calls to resolve or reject have no effect after the first call
  • The utils methods are bound to the task instance

See Also ​

Released under the MIT License.