Hone logo
Hone
Problems

Async Task Scheduler in Vue.js

This challenge requires you to build a reusable Vue.js component that can manage and execute a queue of asynchronous tasks. This is crucial for scenarios where you need to perform multiple non-blocking operations sequentially or with specific delay intervals, providing better user experience by avoiding UI freezes and managing resource usage effectively.

Problem Description

You need to create a Vue.js component, implemented in TypeScript, that acts as an asynchronous task scheduler. This scheduler should allow users to add asynchronous functions (tasks) to a queue and execute them one after another. The component should support basic scheduling features like immediate execution, delayed execution, and the ability to track the progress of the tasks.

Key Requirements:

  1. Task Queueing: The component must maintain a queue of tasks waiting to be executed.
  2. Asynchronous Task Execution: Each task will be an asynchronous function (returning a Promise).
  3. Immediate Execution: Tasks should be executable immediately upon being added if the scheduler is idle.
  4. Delayed Execution: Support for scheduling a task to run after a specified delay.
  5. Progress Tracking: The component should expose reactive properties to indicate:
    • isIdle: Boolean, true if no tasks are currently running or queued for execution.
    • isRunning: Boolean, true if at least one task is currently executing.
    • completedTasks: Number, the count of tasks that have successfully completed.
    • totalTasks: Number, the total number of tasks that have been added to the queue.
  6. Task Management: Provide methods to:
    • addTask(task: () => Promise<any>, delay?: number): Adds a new asynchronous task to the queue. delay is optional and specifies the delay in milliseconds before execution.
    • run(): Manually triggers the execution of the next task in the queue if the scheduler is idle and there are tasks.
    • clearQueue(): Removes all pending tasks from the queue.
  7. Error Handling: If a task rejects its promise, the scheduler should log the error and continue processing the remaining tasks. The isRunning state should be updated accordingly.
  8. Reactivity: All progress tracking properties (isIdle, isRunning, completedTasks, totalTasks) must be reactive using Vue's reactivity system.

Expected Behavior:

  • When addTask is called, the task is added to the queue.
  • If the scheduler is not isRunning and no delays are active, the task (or the next scheduled task) starts executing.
  • If a delay is provided, the task waits for that duration before execution.
  • Tasks are executed sequentially. A new task only starts after the previous one has resolved or rejected.
  • The progress tracking properties should update dynamically as tasks are added and executed.
  • isIdle should be true only when totalTasks is 0 or when all added tasks have been completed or cleared.
  • isRunning should be true when a task is actively being processed (after any delay has passed and before it resolves/rejects).

Edge Cases to Consider:

  • Adding tasks when the scheduler is already running.
  • Adding tasks with a delay of 0.
  • What happens if a task throws a synchronous error before returning a promise? (Assume tasks are well-formed async functions returning Promises for this challenge).
  • Clearing the queue while tasks are running or pending.

Examples

Example 1: Basic Task Execution

// Assuming a Vue component context where scheduler is initialized

// Create a sample async task
const asyncTask1 = () => new Promise(resolve => setTimeout(() => {
  console.log('Task 1 completed');
  resolve('Result 1');
}, 1000));

const asyncTask2 = () => new Promise(resolve => setTimeout(() => {
  console.log('Task 2 completed');
  resolve('Result 2');
}, 500));

// Add tasks to the scheduler
scheduler.addTask(asyncTask1);
scheduler.addTask(asyncTask2);

// Expected Output in the console:
// Task 1 completed
// Task 2 completed

// Expected state updates (simplified representation):
// Initially: isIdle: true, isRunning: false, completedTasks: 0, totalTasks: 0
// After addTask(asyncTask1): isIdle: true, isRunning: false, completedTasks: 0, totalTasks: 1
// When Task 1 starts: isIdle: false, isRunning: true, completedTasks: 0, totalTasks: 1
// After Task 1 completes: isIdle: false, isRunning: true, completedTasks: 1, totalTasks: 1
// When Task 2 starts: isIdle: false, isRunning: true, completedTasks: 1, totalTasks: 2
// After Task 2 completes: isIdle: true, isRunning: false, completedTasks: 2, totalTasks: 2

Example 2: Delayed Task Execution

// Assuming a Vue component context where scheduler is initialized

const delayedTask = () => new Promise(resolve => {
  console.log('Delayed task executed');
  resolve('Delayed Result');
});

// Add a task with a 2-second delay
scheduler.addTask(delayedTask, 2000);

// Expected Output in the console (after 2 seconds):
// Delayed task executed

// Expected state updates:
// Initially: isIdle: true, isRunning: false, completedTasks: 0, totalTasks: 0
// After addTask(delayedTask, 2000): isIdle: true, isRunning: false, completedTasks: 0, totalTasks: 1
// After 2 seconds, when delayedTask starts: isIdle: false, isRunning: true, completedTasks: 0, totalTasks: 1
// After delayedTask completes: isIdle: true, isRunning: false, completedTasks: 1, totalTasks: 1

Example 3: Handling Task Rejection and Clearing Queue

// Assuming a Vue component context where scheduler is initialized

const successfulTask = () => new Promise(resolve => setTimeout(() => resolve('Success'), 500));
const failingTask = () => new Promise((_, reject) => setTimeout(() => reject(new Error('Something went wrong')), 750));
const anotherSuccessfulTask = () => new Promise(resolve => setTimeout(() => resolve('Another Success'), 300));

// Add tasks
scheduler.addTask(successfulTask);
scheduler.addTask(failingTask);
scheduler.addTask(anotherSuccessfulTask);

// In a separate event handler or after some time:
// scheduler.clearQueue();

// Expected Console Output (if clearQueue is NOT called):
// (after 0.5s) successfulTask resolved
// (after 0.75s) Error: Something went wrong
// (after 0.75s + 0.3s) anotherSuccessfulTask resolved

// Expected Console Output (if clearQueue is called immediately after adding tasks):
// (after 0.5s) successfulTask resolved
// (and then the queue is cleared, so failingTask and anotherSuccessfulTask are not executed)

// Expected state updates (if clearQueue is NOT called):
// ... After failingTask completes: isIdle: false, isRunning: true, completedTasks: 1, totalTasks: 3
// ... After anotherSuccessfulTask completes: isIdle: true, isRunning: false, completedTasks: 2, totalTasks: 3 (Note: completedTasks doesn't count rejections)

// Expected state updates (if clearQueue IS called immediately):
// After clearQueue(): isIdle: true, isRunning: false, completedTasks: 0, totalTasks: 0 (all pending tasks are removed)

Constraints

  • The scheduler component must be implemented using Vue 3 Composition API and TypeScript.
  • All reactive state must be managed using Vue's ref or reactive.
  • Task execution should not block the main thread.
  • The delay parameter for addTask will be a non-negative integer.
  • The maximum number of concurrently executing tasks is implicitly one (sequential execution).
  • The maximum number of tasks in the queue is not strictly limited but should be handled efficiently.

Notes

  • Consider using setTimeout for implementing the delays.
  • You will need to manage the state of the scheduler carefully, especially when tasks complete or reject.
  • Think about how to chain the execution of promises. async/await can be very helpful here.
  • The completedTasks count should only increment for successfully resolved promises.
  • The challenge is about creating a utility component, so focus on its logic and API rather than complex UI rendering. You can assume it will be used within a larger Vue application.
Loading editor...
typescript