DEV Community

Cover image for How to Control the Number of Concurrent Promises in JavaScript
Zachary Lee
Zachary Lee

Posted on • Originally published at webdeveloper.beehiiv.com on

1 1

How to Control the Number of Concurrent Promises in JavaScript

In some cases, we may need to control the number of concurrent requests.

For example, when we are writing download or crawling tools, some websites may have a limit on the number of concurrent requests.

In browsers, the maximum number of TCP connections from the same origin is limited to 6. This means that if you use HTTP1.1 when sending more than 6 requests at the same time, the 7th request will wait until the previous one is processed before it starts.

We can use the following simple example to test it. First the client code:

void (async () => {
  await Promise.all(
    [...new Array(12)].map((_, i) =>
      fetch(`http://127.0.0.1:3001/get/${i + 1}`)
    )
  );
})();
Enter fullscreen mode Exit fullscreen mode

Next is the brief code for the service:

router.get('/get/:id', async (ctx) => {
  const order = Number(ctx.params.id);
  if (order % 2 === 0 && order <= 6) {
    await sleep(1000);
  }
  await sleep(1000);
  ctx.body = 1;
});
Enter fullscreen mode Exit fullscreen mode

In the first 6 requests, if the order is even, then wait for 2s , if not, wait for 1s. Then open the Network in DevTools and you can get the following picture:

Looking at the Time and Waterfall columns show that it is a model of the request concurrency limit. So I want to achieve a similar function, how to do it?

I open-sourced p-limiter on Github. Here’s the same Gist:

class Queue<T> {
  #tasks: T[] = [];

  enqueue = (task: T) => {
    this.#tasks.push(task);
  };

  dequeue = () => this.#tasks.shift();

  clear = () => {
    this.#tasks = [];
  };

  get size() {
    return this.#tasks.length;
  }
}

class PromiseLimiter {
  #queue = new Queue<() => Promise<void>>();
  #runningCount = 0;
  #limitCount: number;

  constructor(limitCount: number) {
    this.#limitCount = limitCount;
  }

  #next = () => {
    if (this.#runningCount < this.#limitCount && this.#queue.size > 0) {
      this.#queue.dequeue()?.();
    }
  };

  #run = async <R = any>(
    fn: () => Promise<R>,
    resolve: (value: PromiseLike<R>) => void
  ) => {
    this.#runningCount += 1;
    const result = (async () => fn())();
    resolve(result);

    try {
      await result;
    } catch {
      // ignore
    }

    this.#runningCount -= 1;
    this.#next();
  };

  get activeCount() {
    return this.#runningCount;
  }

  get pendingCount() {
    return this.#queue.size;
  }

  limit = <R = any>(fn: () => Promise<R>) =>
    new Promise<R>((resolve) => {
      this.#queue.enqueue(() => this.#run(fn, resolve));
      this.#next();
    });
}

export default PromiseLimiter;
Enter fullscreen mode Exit fullscreen mode

This can be achieved in less than 70 lines of code above, you can try it out in StackBlitz.

You can see that it works as expected. Without resorting to any third-party libraries, the simple code model is just that.

So do you have any other use cases?

If you find this helpful, please consider subscribing to my newsletter for more insights on web development. Thank you for reading!

SurveyJS custom survey software

Build Your Own Forms without Manual Coding

SurveyJS UI libraries let you build a JSON-based form management system that integrates with any backend, giving you full control over your data with no user limits. Includes support for custom question types, skip logic, an integrated CSS editor, PDF export, real-time analytics, and more.

Learn more

Top comments (0)

Qodo Takeover

Introducing Qodo Gen 1.0: Transform Your Workflow with Agentic AI

Rather than just generating snippets, our agents understand your entire project context, can make decisions, use tools, and carry out tasks autonomously.

Read full post

Best practices for optimal infrastructure performance with Magento

Running a Magento store? Struggling with performance bottlenecks? Join us and get actionable insights and real-world strategies to keep your store fast and reliable.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❤️