Hone logo
Hone
Problems

Asynchronous Task Pooling in JavaScript

Asynchronous operations are fundamental to modern JavaScript development, especially in Node.js environments. Managing a large number of concurrent asynchronous tasks can be inefficient and resource-intensive. This challenge asks you to implement an asynchronous task pool that limits the number of concurrent tasks, improving performance and resource utilization.

Problem Description

You are tasked with creating a JavaScript class called AsyncPool that manages a pool of asynchronous tasks. The pool should accept an array of asynchronous functions (promises) and execute them concurrently, but with a limited number of concurrent executions. The AsyncPool should ensure that no more than maxConcurrency tasks are running at any given time. Once all tasks are completed, the pool should resolve with an array containing the results of each task in the same order as the input array.

Key Requirements:

  • maxConcurrency: The constructor should accept a maxConcurrency argument, which determines the maximum number of tasks that can run concurrently.
  • add(task): A method to add an asynchronous task (a function that returns a Promise) to the pool.
  • run(): A method that initiates the execution of all tasks added to the pool. This method should return a Promise that resolves with an array of results in the original order of the tasks.
  • Error Handling: If any task throws an error, the error should be caught and included in the final result array alongside the other results. The error should be an object with error: true and reason: <error message>.
  • Order Preservation: The results in the resolved array must be in the same order as the tasks were added to the pool.

Expected Behavior:

The run() method should return a Promise. This Promise should resolve when all tasks in the pool have completed (either successfully or with an error). The resolved value should be an array containing the results of each task, preserving the original order.

Edge Cases to Consider:

  • Empty Pool: If the pool is empty when run() is called, it should resolve immediately with an empty array.
  • maxConcurrency of 1: The pool should execute tasks sequentially.
  • maxConcurrency greater than the number of tasks: All tasks should run concurrently.
  • Tasks that reject: Handle promise rejections gracefully and include error information in the result array.
  • Tasks that throw synchronous errors: Handle synchronous errors thrown by the task function.

Examples

Example 1:

Input:
const pool = new AsyncPool(2);
pool.add(() => Promise.resolve(1));
pool.add(() => new Promise(resolve => setTimeout(() => resolve(2), 100)));
pool.add(() => Promise.resolve(3));

Output:
[1, 2, 3]
Explanation: Two tasks run concurrently (1 and 2). Task 3 waits until one of the first two completes. The results are returned in the original order.

Example 2:

Input:
const pool = new AsyncPool(1);
pool.add(() => new Promise(resolve => setTimeout(() => resolve(1), 100)));
pool.add(() => new Promise(resolve => setTimeout(() => resolve(2), 50)));
pool.add(() => new Promise(resolve => setTimeout(() => resolve(3), 75)));

Output:
[1, 2, 3]
Explanation: Tasks run sequentially due to maxConcurrency of 1.

Example 3:

Input:
const pool = new AsyncPool(2);
pool.add(() => Promise.resolve(1));
pool.add(() => () => { throw new Error("Task failed"); })();
pool.add(() => Promise.resolve(3));

Output:
[1, { error: true, reason: "Task failed" }, 3]
Explanation: The first task resolves to 1. The second task throws an error, which is caught and included in the result array. The third task resolves to 3.

Constraints

  • maxConcurrency must be a positive integer.
  • Tasks added to the pool must be functions that return Promises.
  • The pool should handle up to 1000 tasks without significant performance degradation.
  • The run() method should not block the event loop.

Notes

  • Consider using Promise.all() or similar techniques to manage the concurrent execution of tasks.
  • Think about how to efficiently track the number of running tasks and ensure that maxConcurrency is not exceeded.
  • Error handling is crucial. Make sure to catch and report errors from individual tasks without crashing the entire pool.
  • The order of results is paramount. Ensure your implementation preserves the original order of the tasks.
  • Use async/await for cleaner code where appropriate.
Loading editor...
javascript