Implement a TypeScript promise scheduler with bounded concurrency and input-ordered settled results. Invoke each task factory exactly once, handle synchronous throws and asynchronous rejections without fail-fast behavior, reject invalid limits early, and test the concurrency cap deterministically.
# Run Promise Tasks with Bounded Concurrency
The source reports a pure TypeScript or JavaScript coding round involving Promises, but it does not preserve the original problem statement. The following bounded-concurrency exercise is an explicit practice reconstruction, not a claim about the missing original prompt.
Implement this TypeScript function:
```ts
type Settled<T> =
| { status: "fulfilled"; value: T }
| { status: "rejected"; reason: unknown };
async function runWithLimit<T>(
tasks: Array<() => Promise<T>>,
limit: number,
): Promise<Array<Settled<T>>>;
```
Each task is a factory and must not be invoked before a worker slot is available. Invoke every task exactly once, run no more than `limit` task Promises at the same time, and return one settled result per input task in original input order. A task that throws synchronously is treated as rejected. One rejection does not prevent later tasks from starting.
### Constraints & Assumptions
- `tasks` may be empty; return a Promise fulfilled with `[]`.
- `limit` must be a positive integer. Otherwise, the returned Promise rejects with `RangeError` before any task is invoked.
- A task is considered active from immediately before its factory is called until its returned Promise settles.
- The function does not implement cancellation because the source gives no cancellation contract.
- Do not use a library concurrency helper.
### Clarifying Questions to Ask
- Should one rejection stop scheduling, or should all tasks settle?
- Must results preserve input order or completion order?
- Can task factories throw before returning a Promise?
- How should invalid limits, cancellation, and timeouts behave?
- Is fairness beyond input-order scheduling required?
### Hints
- Keep the next unscheduled index separate from the result slot being written.
- Several asynchronous workers can pull indices from one shared counter because JavaScript runs each synchronous counter update to completion.
- Catch both synchronous factory throws and Promise rejections through one Promise chain.
### What a Strong Answer Covers
- At most `limit` active tasks and exactly one invocation per task.
- Input-order results despite out-of-order completion.
- Correct handling of empty input, invalid limits, synchronous throws, and asynchronous rejection.
- No unhandled rejection and no fail-fast behavior under the stated contract.
- A concise explanation of the event loop, microtasks, shared counter safety, complexity, and tests with controllable Promises.
### Follow-up Questions
1. How would you add fail-fast behavior without allowing already-running tasks to produce unhandled rejections?
2. How would an `AbortSignal` change task and scheduler contracts?
3. What changes if tasks are supplied as already-started Promises instead of factories?
4. How would you test the concurrency bound deterministically?
Quick Answer: Implement a TypeScript promise scheduler with bounded concurrency and input-ordered settled results. Invoke each task factory exactly once, handle synchronous throws and asynchronous rejections without fail-fast behavior, reject invalid limits early, and test the concurrency cap deterministically.
The source reports a pure TypeScript or JavaScript coding round involving Promises, but it does not preserve the original problem statement. The following bounded-concurrency exercise is an explicit practice reconstruction, not a claim about the missing original prompt.
Implement this TypeScript function:
type Settled<T> =
| { status: "fulfilled"; value: T }
| { status: "rejected"; reason: unknown };
async function runWithLimit<T>(
tasks: Array<() => Promise<T>>,
limit: number,
): Promise<Array<Settled<T>>>;
Each task is a factory and must not be invoked before a worker slot is available. Invoke every task exactly once, run no more than limit task Promises at the same time, and return one settled result per input task in original input order. A task that throws synchronously is treated as rejected. One rejection does not prevent later tasks from starting.
Constraints & Assumptions
tasks
may be empty; return a Promise fulfilled with
[]
.
limit
must be a positive integer. Otherwise, the returned Promise rejects with
RangeError
before any task is invoked.
A task is considered active from immediately before its factory is called until its returned Promise settles.
The function does not implement cancellation because the source gives no cancellation contract.
Do not use a library concurrency helper.
Clarifying Questions to Ask Guidance
Should one rejection stop scheduling, or should all tasks settle?
Must results preserve input order or completion order?
Can task factories throw before returning a Promise?
How should invalid limits, cancellation, and timeouts behave?
Is fairness beyond input-order scheduling required?
Hints
Keep the next unscheduled index separate from the result slot being written.
Several asynchronous workers can pull indices from one shared counter because JavaScript runs each synchronous counter update to completion.
Catch both synchronous factory throws and Promise rejections through one Promise chain.
What a Strong Answer Covers Guidance
At most
limit
active tasks and exactly one invocation per task.