JSFiddle - React, Tailwind, and code Playground

JavaScript

async function* makeALotOfWork() {
	let value = 0;
  while (true) {
  	yield ({ delay: Math.floor(Math.random() * 1000) + 10, value: value++ });
  }
}

async function* map(fn, n) {
  try {
    // Start n work
    const parallel = new Set();
    for (let i = 0; i < n; ++i) {
    	const itr = await this.next();
      if (itr.done) return;
    	parallel.add({ value: itr.value, promise: fn(itr.value) });
    }
    // Process the work yielding the first one done in the set of n work
	  while (parallel.length != 0) {
  	  const task = await Promise.race([...parallel].map(async currentTask => {
      	await currentTask.promise;
       	return currentTask;
      }));
      parallel.delete(task);
      yield task.value;
      // Add 1 more work
      const itr = await this.next();
      if (itr.done) return;
      parallel.add({ value: itr.value, promise: fn(itr.value) });
    }
  } finally {
    console.log('map done');
    this?.return();
  }
}
[].values().__proto__.__proto__.map = map;
(async function*(){})().__proto__.__proto__.__proto__.map = map;

async function work(t) {
  t.value *= 2;
  await new Promise(resolve => setTimeout(resolve, t.delay));
  return t;
}

(async () => {
  let workDone = 0;
  let totalDelay = 0;
  const currentTime = performance.now();
  for await (const t of makeALotOfWork().map(work, 5).map(work, 5)) {
  	console.log(t.value, t.delay);
    totalDelay += t.delay;
    workDone++;
    if (workDone > 20) {
      break;
    }
  }
  console.log(`Work Total Time: ${totalDelay} Elapsed Time: ${performance.now() - currentTime}`);
})();