In this section, we will delve into more complex asynchronous programming patterns in TypeScript. These patterns are essential for building robust and efficient applications that handle multiple asynchronous operations seamlessly.
Key Concepts
- Concurrency vs. Parallelism
- Promise.all, Promise.race, and Promise.allSettled
- Async Iterators
- Cancellation of Promises
- Error Handling in Complex Async Flows
Concurrency vs. Parallelism
- Concurrency: Multiple tasks make progress over time. They may not run simultaneously but are managed in a way that they appear to be running at the same time.
- Parallelism: Multiple tasks run simultaneously, typically on multiple processors or cores.
Example
// Concurrency Example 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 runTasksConcurrently() { await Promise.all([task1(), task2()]); console.log("Both tasks completed concurrently"); } runTasksConcurrently();
Promise.all, Promise.race, and Promise.allSettled
Promise.all
- Waits for all promises to resolve or any to reject.
const promise1 = Promise.resolve(3); const promise2 = 42; const promise3 = new Promise((resolve, reject) => { setTimeout(resolve, 100, 'foo'); }); Promise.all([promise1, promise2, promise3]).then((values) => { console.log(values); // [3, 42, "foo"] });
Promise.race
- Resolves or rejects as soon as one of the promises resolves or rejects.
const promise1 = new Promise((resolve, reject) => { setTimeout(resolve, 500, 'one'); }); const promise2 = new Promise((resolve, reject) => { setTimeout(resolve, 100, 'two'); }); Promise.race([promise1, promise2]).then((value) => { console.log(value); // "two" });
Promise.allSettled
- Waits for all promises to either resolve or reject.
const promise1 = Promise.resolve(3); const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo')); const promise3 = 42; Promise.allSettled([promise1, promise2, promise3]).then((results) => { results.forEach((result) => console.log(result.status)); // "fulfilled" // "rejected" // "fulfilled" });
Async Iterators
- Allows asynchronous iteration over data.
Example
async function* asyncGenerator() { let i = 0; while (i < 3) { yield new Promise(resolve => setTimeout(() => resolve(i++), 1000)); } } (async () => { for await (const num of asyncGenerator()) { console.log(num); // 0, 1, 2 } })();
Cancellation of Promises
- Using
AbortController
to cancel promises.
Example
const controller = new AbortController(); const signal = controller.signal; const fetchData = async (url: string) => { try { const response = await fetch(url, { signal }); const data = await response.json(); console.log(data); } catch (error) { if (error.name === 'AbortError') { console.log('Fetch aborted'); } else { console.error('Fetch error:', error); } } }; fetchData('https://jsonplaceholder.typicode.com/todos/1'); controller.abort(); // Aborts the fetch request
Error Handling in Complex Async Flows
- Using try-catch blocks and handling errors in nested async functions.
Example
async function fetchData(url: string) { try { const response = await fetch(url); if (!response.ok) { throw new Error('Network response was not ok'); } const data = await response.json(); return data; } catch (error) { console.error('Fetch error:', error); throw error; } } async function processData() { try { const data = await fetchData('https://jsonplaceholder.typicode.com/todos/1'); console.log('Data:', data); } catch (error) { console.error('Process error:', error); } } processData();
Practical Exercises
Exercise 1: Using Promise.all
Write a function that fetches data from three different URLs concurrently and logs the results.
const urls = [ 'https://jsonplaceholder.typicode.com/todos/1', 'https://jsonplaceholder.typicode.com/todos/2', 'https://jsonplaceholder.typicode.com/todos/3' ]; async function fetchAllData(urls: string[]) { try { const promises = urls.map(url => fetch(url).then(response => response.json())); const results = await Promise.all(promises); console.log(results); } catch (error) { console.error('Error fetching data:', error); } } fetchAllData(urls);
Exercise 2: Using Async Iterators
Create an async generator that yields numbers from 1 to 5 with a delay of 1 second between each number.
async function* numberGenerator() { for (let i = 1; i <= 5; i++) { yield new Promise(resolve => setTimeout(() => resolve(i), 1000)); } } (async () => { for await (const num of numberGenerator()) { console.log(num); // 1, 2, 3, 4, 5 } })();
Summary
In this section, we explored advanced asynchronous patterns in TypeScript, including concurrency vs. parallelism, Promise combinators, async iterators, cancellation of promises, and error handling in complex async flows. These patterns are crucial for building efficient and robust applications that handle multiple asynchronous operations seamlessly. By mastering these concepts, you will be well-equipped to tackle complex asynchronous programming challenges in your TypeScript projects.
TypeScript Course
Module 1: Introduction to TypeScript
- What is TypeScript?
- Setting Up the TypeScript Environment
- Basic Types
- Type Annotations
- Compiling TypeScript
Module 2: Working with Types
Module 3: Advanced Types
Module 4: Functions and Modules
Module 5: Asynchronous Programming
Module 6: Tooling and Best Practices
- Linting and Formatting
- Testing TypeScript Code
- TypeScript with Webpack
- TypeScript with React
- Best Practices