Follow these instructions to create and understand various async and sync operations in JavaScript.
- Open your JavaScript environment (e.g., a
index.ts
file to run with bun, probably). - Create a synchronous function:
function syncFunction() { console.log("Sync function executed"); }
- Create an asynchronous function using setTimeout:
function asyncFunction() { setTimeout(() => { console.log("Async function executed"); }, 1000); }
- Call both functions and add a console.log after:
syncFunction(); asyncFunction(); console.log("Code after function calls");
- Run the code and observe the order of execution.
- Create a function demonstrating callback hell:
function callbackHell() { setTimeout(() => { console.log("First callback"); setTimeout(() => { console.log("Second callback"); setTimeout(() => { console.log("Third callback"); }, 1000); }, 1000); }, 1000); }
- Create a function using Promises to achieve the same result:
function promiseChain() { return new Promise((resolve) => { setTimeout(() => { console.log("First promise"); resolve(); }, 1000); }) .then(() => { return new Promise((resolve) => { setTimeout(() => { console.log("Second promise"); resolve(); }, 1000); }); }) .then(() => { return new Promise((resolve) => { setTimeout(() => { console.log("Third promise"); resolve(); }, 1000); }); }); }
- Call both functions and compare the readability and structure.
- Create an async function using the async/await syntax:
async function asyncAwaitExample() { console.log("Start of async function"); await new Promise(resolve => setTimeout(resolve, 1000)); console.log("After 1 second"); await new Promise(resolve => setTimeout(resolve, 1000)); console.log("After 2 seconds"); console.log("End of async function"); }
- Call the function and add a console.log after:
asyncAwaitExample(); console.log("Code after async function call");
- Run the code and observe how async/await makes asynchronous code look more synchronous.
- Create a function for sequential fetch operations (note: replace with actual API endpoints):
async function sequentialFetch() { const start = Date.now(); const response1 = await fetch('https://api.example.com/data1'); const response2 = await fetch('https://api.example.com/data2'); console.log(`Sequential fetch took ${Date.now() - start} ms`); }
- Create a function for parallel fetch operations:
async function parallelFetch() { const start = Date.now(); const [response1, response2] = await Promise.all([ fetch('https://api.example.com/data1'), fetch('https://api.example.com/data2') ]); console.log(`Parallel fetch took ${Date.now() - start} ms`); }
- Call both functions and compare the execution times.
- Create a function that simulates an async operation with a success/fail condition:
function simulateAsyncOperation(shouldSucceed) { return new Promise((resolve, reject) => { setTimeout(() => { if (shouldSucceed) { resolve("Operation successful"); } else { reject(new Error("Operation failed")); } }, 1000); }); }
- Create an async function to handle errors:
async function handleAsyncErrors() { try { console.log(await simulateAsyncOperation(true)); console.log(await simulateAsyncOperation(false)); } catch (error) { console.error("Caught an error:", error.message); } console.log("Continuing after error"); }
- Call the function and observe how errors are handled.
- Create a function that returns a promise resolving after a given timeout:
function asyncOperationWithTimeout(timeout) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`Operation completed in ${timeout}ms`); }, timeout); }); }
- Create a function using Promise.race():
async function raceExample() { const result = await Promise.race([ asyncOperationWithTimeout(2000), asyncOperationWithTimeout(1000), asyncOperationWithTimeout(3000) ]); console.log(result); }
- Call the function and observe which promise resolves first.
Note: This example typically runs in a browser environment.
- Create a main script:
function runWorker() { const worker = new Worker('worker.js'); worker.postMessage({ number: 42 }); worker.onmessage = function(event) { console.log('Result from worker:', event.data); }; }
- Create a separate 'worker.js' file:
self.onmessage = function(event) { const number = event.data.number; let result = 0; // Simulate a CPU-intensive task for (let i = 0; i < 1000000000; i++) { result += Math.sqrt(number); } self.postMessage(result); };
- Run the main script in a browser environment and observe how the worker performs the CPU-intensive task without blocking the main thread.
These examples demonstrate how to work with async operations in React, particularly using the useEffect hook. Make sure you have a React environment set up before proceeding.
- Create a new functional component:
import React, { useState, useEffect } from 'react'; function DataFetcher() { const [data, setData] = useState(null); useEffect(() => { fetch('https://api.example.com/data') .then(response => response.json()) .then(result => setData(result)) .catch(error => console.error('Error fetching data:', error)); }, []); if (!data) return <div>Loading...</div>; return <div>{JSON.stringify(data)}</div>; }
- Use this component in your React app.
- Observe how the data is fetched when the component mounts and how the UI updates once the data is available.
- Modify the previous example to use async/await:
import React, { useState, useEffect } from 'react'; function AsyncDataFetcher() { const [data, setData] = useState(null); useEffect(() => { async function fetchData() { try { const response = await fetch('https://api.example.com/data'); const result = await response.json(); setData(result); } catch (error) { console.error('Error fetching data:', error); } } fetchData(); }, []); if (!data) return <div>Loading...</div>; return <div>{JSON.stringify(data)}</div>; }
- Use this component in your React app.
- Notice how the async function is defined inside useEffect and then called immediately.
- Create a component that simulates a long-running async operation:
import React, { useState, useEffect } from 'react'; function AsyncCounter() { const [count, setCount] = useState(0); useEffect(() => { let isMounted = true; const intervalId = setInterval(() => { if (isMounted) { setCount(prevCount => prevCount + 1); } }, 1000); return () => { isMounted = false; clearInterval(intervalId); }; }, []); return <div>Count: {count}</div>; }
- Use this component in your React app, perhaps with a toggle to mount/unmount it.
- Observe how the cleanup function prevents state updates after the component unmounts.
- Create a component that fetches data based on user input:
import React, { useState, useEffect } from 'react'; function DebouncedSearch() { const [query, setQuery] = useState(''); const [results, setResults] = useState([]); useEffect(() => { const debounceTimeout = setTimeout(() => { if (query) { fetch(`https://api.example.com/search?q=${query}`) .then(response => response.json()) .then(data => setResults(data)) .catch(error => console.error('Error searching:', error)); } }, 300); return () => clearTimeout(debounceTimeout); }, [query]); return ( <div> <input type="text" value={query} onChange={e => setQuery(e.target.value)} placeholder="Search..." /> <ul> {results.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> </div> ); }
- Implement this component in your React app.
- Test it by typing in the search box and observe how the API calls are debounced.
- Create a component that fetches data from multiple APIs in parallel:
import React, { useState, useEffect } from 'react'; function ParallelDataFetcher() { const [userData, setUserData] = useState(null); const [postsData, setPostsData] = useState(null); useEffect(() => { async function fetchData() { try { const [userResponse, postsResponse] = await Promise.all([ fetch('https://api.example.com/user'), fetch('https://api.example.com/posts') ]); const userData = await userResponse.json(); const postsData = await postsResponse.json(); setUserData(userData); setPostsData(postsData); } catch (error) { console.error('Error fetching data:', error); } } fetchData(); }, []); if (!userData || !postsData) return <div>Loading...</div>; return ( <div> <h2>User: {userData.name}</h2> <h3>Posts:</h3> <ul> {postsData.map(post => ( <li key={post.id}>{post.title}</li> ))} </ul> </div> ); }
- Implement this component in your React app.
- Observe how both API calls are made in parallel, potentially improving load times.
These examples demonstrate key concepts of working with async operations in React:
- Basic data fetching using useEffect
- Using async/await syntax within useEffect
- Proper cleanup of async operations to prevent memory leaks
- Debouncing API calls for performance optimization
- Making parallel API calls for efficiency