DEV Community

Cover image for Mastering Concurrency and Parallelism in TypeScript
Shafayet Hossain
Shafayet Hossain

Posted on

Mastering Concurrency and Parallelism in TypeScript

Modern applications demand high performance and responsiveness, requiring developers to master concurrency and parallelism. TypeScript, as a superset of JavaScript, provides robust tools and patterns for managing these complexities. This guide explores both concepts from all perspectives, diving deep into practical examples, patterns, and advanced practices for leveraging concurrency and parallelism in TypeScript.

Concurrency vs. Parallelism: Key Differences

Before going into code, it’s crucial to understand these terms:

1.Concurrency:

  • Definition: The ability of a system to handle multiple tasks by interleaving their execution (not necessarily at the same time).
  • Example: Switching between handling a database query and processing a file upload in an event loop.

2.Parallelism:

  • Definition: Performing multiple tasks simultaneously by leveraging multi-core processors.
  • Example: Performing complex mathematical computations on different cores simultaneously.

Visualization:
Imagine a restaurant:

  • Concurrency: A single chef multitasking between several dishes.
  • Parallelism: Multiple chefs working on separate dishes simultaneously.

Concurrency in TypeScript

JavaScript, and by extension TypeScript, runs on a single-threaded event loop, which might make concurrency sound impossible. However, concurrency is achieved through asynchronous programming models like callbacks, promises, and async/await.

1. Using Promises for Concurrency
Promises are one of the simplest ways to achieve concurrency in TypeScript.

const fetchData = (url: string) => {
  return new Promise<string>((resolve) => {
    setTimeout(() => resolve(`Data from ${url}`), 1000);
  });
};

const main = async () => {
  console.log('Fetching data concurrently...');
  const data1 = fetchData('https://api.example.com/1');
  const data2 = fetchData('https://api.example.com/2');

  const results = await Promise.all([data1, data2]);
  console.log(results); // ["Data from https://api.example.com/1", "Data from https://api.example.com/2"]
};
main();
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Promise.all allows both fetch operations to run concurrently, saving time. 2. Concurrency with Async/Await async/await simplifies promise chaining while maintaining the asynchronous nature.
async function task1() {
  console.log("Task 1 started");
  await new Promise((resolve) => setTimeout(resolve, 2000));
  console.log("Task 1 completed");
}

async function task2() {
  console.log("Task 2 started");
  await new Promise((resolve) => setTimeout(resolve, 1000));
  console.log("Task 2 completed");
}

async function main() {
  console.log("Concurrent execution...");
  await Promise.all([task1(), task2()]);
  console.log("All tasks completed");
}
main();
Enter fullscreen mode Exit fullscreen mode

Parallelism in TypeScript

While JavaScript doesn’t natively support multi-threading, Web Workers and Node.js Worker Threads enable parallelism. These features leverage separate threads to handle computationally expensive tasks.

1. Web Workers for Parallelism
In browser environments, Web Workers execute scripts in a separate thread.

// worker.ts
addEventListener('message', (event) => {
  const result = event.data.map((num: number) => num * 2);
  postMessage(result);
});
Enter fullscreen mode Exit fullscreen mode
// main.ts
const worker = new Worker('worker.js');

worker.onmessage = (event) => {
  console.log('Result from worker:', event.data);
};

worker.postMessage([1, 2, 3, 4]);
Enter fullscreen mode Exit fullscreen mode



2. Node.js Worker Threads
For server-side applications, Node.js provides worker_threads.

// worker.js
const { parentPort } = require('worker_threads');
parentPort.on('message', (data) => {
  const result = data.map((num) => num * 2);
  parentPort.postMessage(result);
});
Enter fullscreen mode Exit fullscreen mode
// main.js
const { Worker } = require('worker_threads');

const worker = new Worker('./worker.js');
worker.on('message', (result) => {
  console.log('Worker result:', result);
});
worker.postMessage([1, 2, 3, 4]);
Enter fullscreen mode Exit fullscreen mode

Patterns for Effective Concurrency and Parallelism

1. Task Queues for Managing Concurrency
When dealing with many tasks, task queues ensure controlled execution.

class TaskQueue {
  private queue: (() => Promise<void>)[] = [];
  private running = 0;
  constructor(private concurrencyLimit: number) {}

  enqueue(task: () => Promise<void>) {
    this.queue.push(task);
    this.run();
  }

  private async run() {
    if (this.running >= this.concurrencyLimit || this.queue.length === 0) return;

    this.running++;
    const task = this.queue.shift();
    if (task) await task();
    this.running--;
    this.run();
  }
}

// Usage
const queue = new TaskQueue(3);
for (let i = 0; i < 10; i++) {
  queue.enqueue(async () => {
    console.log(`Task ${i} started`);
    await new Promise((resolve) => setTimeout(resolve, 1000));
    console.log(`Task ${i} completed`);
  });
}
Enter fullscreen mode Exit fullscreen mode



2. Load Balancing with Worker Pools
Worker pools efficiently distribute tasks across multiple workers.

import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';

if (isMainThread) {
  const workers = Array.from({ length: 4 }, () => new Worker(__filename));
  const tasks = [10, 20, 30, 40];
  workers.forEach((worker, index) => {
    worker.postMessage(tasks[index]);
    worker.on('message', (result) => console.log('Result:', result));
  });
} else {
  parentPort.on('message', (task) => {
    parentPort.postMessage(task * 2);
  });
}
Enter fullscreen mode Exit fullscreen mode

Challenges and Solutions

1. Debugging Asynchronous Code

  • Use tools like async_hooks in Node.js to trace async operations.
  • Use IDEs that support debugging async/await code.

2. Error Handling

  • Wrap promises in try/catch blocks or use .catch() with Promise.all.

3. Race Conditions
Avoid shared state or use locking mechanisms.

Best Practices for Concurrency and Parallelism

1. Prioritize Asynchronous I/O: Avoid blocking the main thread for I/O-heavy operations.
2. Use Worker Threads for CPU-Intensive Tasks: Offload heavy computations to worker threads or Web Workers.
3. Limit Concurrency: Use task queues or libraries like p-limit to control concurrency levels.
4. Leverage Libraries: Use libraries like Bull for task queues or Workerpool for worker thread management.

Conclusion
Concurrency and parallelism are vital for building high-performance, scalable TypeScript applications. While concurrency improves responsiveness by interleaving tasks, parallelism enables simultaneous execution on multi-core systems. By mastering these concepts, developers can tackle challenges in modern applications and deliver seamless user experiences.


My personal website: https://shafayet.zya.me


This is How to close Vim...😭😭😭

Image description

Top comments (16)

Collapse
 
quanla profile image
Quan Le

Is it possible to do these things in plain JavaScript?

Collapse
 
shafayeat profile image
Shafayet Hossain

Yes, these concepts can be applied in plain JavaScript! TypeScript is a superset of JavaScript, meaning it adds type checking and features like type guards on top of JavaScript's existing functionality. The examples involving Promises, async/await, and Worker Threads work just the same in plain JavaScript. The main advantage of TypeScript is the added type safety, which helps catch errors during development. For instance, Type Guards ensure type correctness at runtime. If you're working on a large-scale project, this can make a significant difference.

Collapse
 
ocierd profile image
Fernando Ricardo

All JS libraries are written in plain JS

Collapse
 
vandeurenglenn profile image
Glenn Vandeuren

There is almost no typescript involved, except for the private fields, so why does this title include typescript and not JavaScript?

Collapse
 
shafayeat profile image
Shafayet Hossain

Thanks for pointing that out, Glenn! The title focuses on TypeScript because of the type safety and private fields highlighted here. I’ll keep this balance in mind for future posts, appreciate your input!!!😊

Collapse
 
eriadura profile image
SAMUEL ADENIJI

thank you

Collapse
 
eriadura profile image
SAMUEL ADENIJI

there is almost a typescript

Collapse
 
gabrielc10 profile image
Gabriel Carvalho

Interesting, nice article!

Collapse
 
shafayeat profile image
Shafayet Hossain

So happy you liked it! Means a lot😊

Collapse
 
eriadura profile image
SAMUEL ADENIJI

so happy to see that

Collapse
 
nozibul_islam_113b1d5334f profile image
Nozibul Islam

Great article.

Collapse
 
shafayeat profile image
Shafayet Hossain

So happy to hear you enjoyed it!🖤

Collapse
 
nathbabs profile image
Nathaniel

Well Tbh Parallelism isn't really possible with JavaScript/Typescript....you would have to look to languages like C++

Collapse
 
shafayeat profile image
Shafayet Hossain

You're right, Nathaniel! True parallelism isn’t JavaScript/TypeScript’s strong suit since they’re single-threaded by nature. However, we can achieve something close using Web Workers or Node.js Worker Threads for tasks like CPU-intensive operations. It’s not on the same level as C++'s multithreading but works well within JS’s ecosystem...😅

Collapse
 
hannahanot profile image
Hannah

i am prob the girl with the m16 after trying to learn this!

Collapse
 
shafayeat profile image
Shafayet Hossain

Thanks, Hannah! 😄 That’s the spirit of learning something challenging, fighting through it like a pro!!! Keep going, and you’ll be amazed at what you can accomplish...Trust me😉😉

Some comments may only be visible to logged-in visitors. Sign in to view all comments.