Concurrency: A single chef multitasking between several dishes. Parallelism: Multiple chefs working on separate dishes simultaneously. 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(); 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(); 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); }); // main.ts const worker = new Worker('worker.js'); worker.onmessage = (event) => { console.log('Result from worker:', event.data); }; worker.postMessage([1, 2, 3, 4]); 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); }); // 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]); 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`); }); } 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); }); } 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.
Preview:
downloadDownload PNG
downloadDownload JPEG
downloadDownload SVG
Tip: You can change the style, width & colours of the snippet with the inspect tool before clicking Download!
Click to optimize width for Twitter