We use cookies to enhance your experience on the site
CodeWorlds

Async/Await — Elegant Asynchronous Code

In previous exercises we learned about callbacks and Promises. Now it is time to learn about

async/await
— syntax introduced in ES2017 that makes asynchronous code look and behave almost like synchronous code.

In Jurassic Park, imagine you are a scientist who needs to: collect a DNA sample, sequence it, synthesize a protein, grow cells, and finally create a dinosaur. With

async/await
this process reads like a straightforward step-by-step procedure, even though each step is asynchronous!

The async Keyword

Placing

async
before a function declaration makes it an async function. An async function:

  • Always returns a Promise (even if you
    return
    a plain value)
  • Can use the
    await
    keyword inside its body
1// Regular function — returns undefined
2function getName() {
3  return "Rexy";
4}
5
6// Async function — returns Promise<string>
7async function getDinosaurName() {
8  return "Rexy";  // Automatically wrapped in Promise.resolve("Rexy")
9}
10
11getName();                  // "Rexy"
12getDinosaurName();          // Promise { 'Rexy' }
13getDinosaurName().then(name => console.log(name)); // "Rexy"

The await Keyword

await
can only be used inside an
async
function. It pauses execution of the async function until the Promise settles, then resumes with the resolved value.

1function fetchDinosaurData(id) {
2  return new Promise(resolve => {
3    setTimeout(() => resolve({ id, name: "Blue", species: "Velociraptor", health: 98 }), 1000);
4  });
5}
6
7// Without async/await — using .then()
8function showDinoInfoOld(id) {
9  return fetchDinosaurData(id).then(dino => {
10    console.log(`${dino.name} (${dino.species}) — health: ${dino.health}%`);
11    return dino;
12  });
13}
14
15// With async/await — reads like synchronous code
16async function showDinoInfo(id) {
17  const dino = await fetchDinosaurData(id); // Pauses here until Promise resolves
18  console.log(`${dino.name} (${dino.species}) — health: ${dino.health}%`);
19  return dino;
20}
21
22showDinoInfo(1);

Comparison: Promise Chains vs async/await

Let's compare the same functionality implemented with traditional Promise syntax and async/await:

1// Using traditional Promises
2function checkSecuritySystems() {
3  console.log("Checking security systems...");
4
5  return fetchGateStatus()
6    .then(gateStatus => {
7      console.log("Gate status:", gateStatus.allClosed ? "All closed" : "WARNING! Some are open");
8
9      return fetchFenceStatus();
10    })
11    .then(fenceStatus => {
12      console.log("Fence:", fenceStatus.power > 8000 ? "Secured" : "Insufficient power!");
13
14      return fetchCameraStatus();
15    })
16    .then(cameraStatus => {
17      console.log("Cameras:", cameraStatus.allOperational ? "All operational" : "Some are not working!");
18
19      return {
20        gate: gateStatus,
21        fence: fenceStatus,
22        cameras: cameraStatus,
23        checkDuration: new Date().toISOString()
24      };
25    })
26    .catch(error => {
27      console.error("Error during check:", error.message);
28      throw new Error(`Check failed: ${error.message}`);
29    });
30}
31
32// Using async/await
33async function checkSecuritySystems() {
34  console.log("Checking security systems...");
35
36  try {
37    // Each of the following calls pauses function execution
38    const gateStatus = await fetchGateStatus();
39    console.log("Gate status:", gateStatus.allClosed ? "All closed" : "WARNING! Some are open");
40
41    const fenceStatus = await fetchFenceStatus();
42    console.log("Fence:", fenceStatus.power > 8000 ? "Secured" : "Insufficient power!");
43
44    const cameraStatus = await fetchCameraStatus();
45    console.log("Cameras:", cameraStatus.allOperational ? "All operational" : "Some are not working!");
46
47    return {
48      gate: gateStatus,
49      fence: fenceStatus,
50      cameras: cameraStatus,
51      checkDuration: new Date().toISOString()
52    };
53  } catch (error) {
54    console.error("Error during check:", error.message);
55    throw new Error(`Check failed: ${error.message}`);
56  }
57}

Notice that:

  • Code using async/await is significantly more readable — it looks almost like synchronous code
  • It is easier to track the data flow (values
    gateStatus
    ,
    fenceStatus
    ,
    cameraStatus
    )
  • Error handling is simpler, using standard
    try/catch
    syntax instead of
    .catch()

Converting Promise-Returning Functions to async/await

Any function that returns a Promise can be transformed into an async function:

1// Function returning a Promise
2function measureEnclosureTemperature(enclosureId) {
3  return new Promise((resolve, reject) => {
4    // Measurement code...
5  });
6}
7
8// Transformed to async function
9async function measureEnclosureTemperature(enclosureId) {
10  // We can use asynchronous operations with await
11  const sensor = await connectToSensor(enclosureId);
12  const reading = await sensor.getTemperature();
13
14  if (reading.status === "error") {
15    throw new Error(`Temperature reading error: ${reading.message}`);
16  }
17
18  return {
19    enclosureId,
20    temperature: reading.value,
21    unit: "Celsius",
22    measurementTime: new Date()
23  };
24}

Error Handling with try/catch

Instead of

.catch()
, async functions use regular
try/catch
:

1async function performHealthCheck(dinoId) {
2  try {
3    const dino = await fetchDinosaurData(dinoId);
4    const enclosure = await fetchEnclosureStatus(dino.enclosureId);
5    const vetReport = await fetchVetReport(dinoId);
6
7    return {
8      dinosaur: dino.name,
9      enclosureOk: enclosure.status === "secure",
10      lastCheckup: vetReport.date,
11      overall: "healthy"
12    };
13  } catch (error) {
14    console.error(`Health check failed for dinosaur ${dinoId}: ${error.message}`);
15    return { dinosaur: dinoId, overall: "unknown", error: error.message };
16  } finally {
17    console.log(`Health check routine complete for dinosaur ${dinoId}`);
18  }
19}
20
21// Stub functions
22function fetchEnclosureStatus(id) {
23  return Promise.resolve({ id, status: "secure", lastInspected: "2024-01-15" });
24}
25function fetchVetReport(id) {
26  return Promise.resolve({ dinoId: id, date: "2024-01-10", notes: "All clear" });
27}
28
29performHealthCheck(42).then(report => console.log(report));

Detailed Error Handling Example

1async function releaseDinosaurFromEnclosure(dinoId, enclosureId) {
2  try {
3    // Check if we have permissions
4    const permissions = await checkPermissions();
5    if (!permissions.canReleaseAnimals) {
6      throw new Error("No permission to release dinosaurs");
7    }
8
9    // Check dinosaur status
10    const dinosaurStatus = await checkDinosaurStatus(dinoId);
11    if (dinosaurStatus.health !== "Good") {
12      throw new Error(`Dinosaur #${dinoId} nie jest w dobrym stanie zdrowia`);
13    }
14
15    // Check if the enclosure is secure
16    const enclosureStatus = await checkEnclosureStatus(enclosureId);
17    if (!enclosureStatus.isSecure) {
18      throw new Error(`Wybieg #${enclosureId} nie jest bezpieczny`);
19    }
20
21    // Open the enclosure gate
22    await openEnclosureGateById(enclosureId);
23    console.log(`Brama enclosureu #${enclosureId} otwarta`);
24
25    // Release the dinosaur
26    const result = await transferDinosaur(dinoId, enclosureId, "outdoor-area");
27    console.log(`Dinosaur #${dinoId} has been released to the outdoor area of enclosure #${enclosureId}`);
28
29    return {
30      success: true,
31      message: `Dinosaur #${dinoId} has been successfully released`,
32      details: result
33    };
34  } catch (error) {
35    console.error("Dinosaur release operation failed:", error.message);
36
37    // We can attempt to fix the error or perform cleanup operations
38    try {
39      await closeAllGates();
40      await activateSecuritySystem();
41    } catch (cleanupError) {
42      console.error("Error during cleanup operations:", cleanupError.message);
43    }
44
45    return {
46      success: false,
47      message: `Error while releasing dinosaur: ${error.message}`,
48      error: error.message
49    };
50  }
51}

In the above example we used a

try/catch
block to handle any errors that may occur during the dinosaur release process. We can also nest
try/catch
blocks, as seen in the error handler where we attempt cleanup operations that may also fail.

Asynchronous Iterations

The introduction of async/await also enabled asynchronous iterations, which are particularly useful when working with collections of objects that require asynchronous operations.

Sequential Collection Processing

1async function examineAllDinosaurs(idList) {
2  console.log(`Starting examination of ${idList.length} dinosaurs...`);
3
4  const results = [];
5
6  // Sequential processing (one after another)
7  for (const id of idList) {
8    try {
9      const result = await examineDinosaur(id);
10      console.log(`Dinosaur #${id} przebadany: ${result.status}`);
11      results.push({
12        id,
13        success: true,
14        result: result
15      });
16    } catch (error) {
17      console.error(`Error examining dinosaur #${id}: ${error.message}`);
18      results.push({
19        id,
20        success: false,
21        error: error.message
22      });
23    }
24  }
25
26  return {
27    totalExaminations: idList.length,
28    successful: results.filter(w => w.success).length,
29    niesuccessful: results.filter(w => !w.success).length,
30    results
31  };
32}

In the above example we use

for...of
with
await
inside the loop, which causes dinosaur examinations to be performed one after another. This is the appropriate approach when order matters or when operations must be performed sequentially.

Parallel Collection Processing

If operations don't need to be performed sequentially, it's often better to process the collection in parallel:

1async function examineAllDinosaursInParallel(idList) {
2  console.log(`Starting examination of ${idList.length} dinosaurs in parallel...`);
3
4  // Creating an array of promises
5  const promises = idList.map(id => {
6    return examineDinosaur(id)
7      .then(result => ({
8        id,
9        success: true,
10        result: result
11      }))
12      .catch(error => ({
13        id,
14        success: false,
15        error: error.message
16      }));
17  });
18
19  // Parallel execution of all examinations
20  const results = await Promise.all(promises);
21
22  return {
23    totalExaminations: idList.length,
24    successful: results.filter(w => w.success).length,
25    niesuccessful: results.filter(w => !w.success).length,
26    results
27  };
28}

In this case we use

map()
to create an array of Promises and then use
Promise.all()
with
await
to wait for all operations to complete. This way all examinations are performed in parallel, which can significantly speed up the processing of large collections.

Parallel Processing with a Limit

Sometimes we don't want to perform all operations simultaneously as that could overload the system. In such cases we can limit the number of parallel operations:

1async function examineDinosaursWithLimit(idList, maxParallel = 3) {
2  console.log(`Starting examination of ${idList.length} dinosaurs (max ${maxParallel} in parallel)...`);
3
4  const results = [];
5
6  // Processing in batches
7  for (let i = 0; i < idList.length; i += maxParallel) {
8    const batch = idList.slice(i, i + maxParallel);
9    console.log(`Processing batch ${Math.floor(i / maxParallel) + 1} (${batch.length} dinosaurs)...`);
10
11    // Creating promise array for the current batch
12    const promisesPorcji = batch.map(id => {
13      return examineDinosaur(id)
14        .then(result => ({
15          id,
16          success: true,
17          result: result
18        }))
19        .catch(error => ({
20          id,
21          success: false,
22          error: error.message
23        }));
24    });
25
26    // Parallel execution of examinations in the current batch
27    const resultsPorcji = await Promise.all(promisesPorcji);
28    results.push(...resultsPorcji);
29
30    console.log(`Batch ${Math.floor(i / maxParallel) + 1} completed.`);
31  }
32
33  return {
34    totalExaminations: idList.length,
35    successful: results.filter(w => w.success).length,
36    niesuccessful: results.filter(w => !w.success).length,
37    results
38  };
39}

In this case we process the ID list in batches of

maxParallel
elements. Each batch is processed in parallel, but we wait for all operations in one batch to complete before moving to the next.

Sequential vs Parallel Execution

One of the most important concepts with async/await is knowing when execution is sequential and when it is parallel:

1// SEQUENTIAL — each awaits before the next starts
2async function sequentialCheck() {
3  console.time("sequential");
4  const fence = await checkFenceSystem("A");   // ~1 second
5  const camera = await checkCameraSystem("North"); // ~1 second
6  const sensor = await checkMotionSensors("B");    // ~1 second
7  console.timeEnd("sequential"); // ~3 seconds total
8  return [fence, camera, sensor];
9}
10
11// PARALLEL — all start at the same time, await all results
12async function parallelCheck() {
13  console.time("parallel");
14  const [fence, camera, sensor] = await Promise.all([
15    checkFenceSystem("A"),
16    checkCameraSystem("North"),
17    checkMotionSensors("B")
18  ]);
19  console.timeEnd("parallel"); // ~1 second total
20  return [fence, camera, sensor];
21}
22
23// Stub functions
24function checkFenceSystem(id) { return new Promise(res => setTimeout(() => res({ sector: id, ok: true }), 800)); }
25function checkCameraSystem(id) { return new Promise(res => setTimeout(() => res({ zone: id, ok: true }), 900)); }
26function checkMotionSensors(id) { return new Promise(res => setTimeout(() => res({ area: id, ok: true }), 700)); }

Rule of thumb:

  • Use
    await
    one-by-one only when each operation depends on the result of the previous one
  • Use
    Promise.all()
    with
    await
    when operations are independent — it is much faster
1async function prepareDinoReport(dinoId) {
2  // Step 1: Must happen first (ID needed for step 2)
3  const dino = await fetchDinosaurData(dinoId);
4
5  // Steps 2-4 are independent of each other — run in parallel
6  const [enclosure, vetRecord, feedingLog] = await Promise.all([
7    fetchEnclosureStatus(dino.enclosureId),
8    fetchVetReport(dino.id),
9    fetchFeedingLog(dino.id)
10  ]);
11
12  return { dino, enclosure, vetRecord, feedingLog };
13}
14
15function fetchFeedingLog(id) {
16  return Promise.resolve({ dinoId: id, lastFed: "2024-01-15 08:00", amount: "50kg" });
17}

Async Arrow Functions and Class Methods

async
works with all function forms:

1// Async arrow function
2const getDinoStatus = async (id) => {
3  const data = await fetchDinosaurData(id);
4  return data.health > 80 ? "healthy" : "needs attention";
5};
6
7// Async class method
8class DinoMonitor {
9  constructor(parkId) {
10    this.parkId = parkId;
11  }
12
13  async runFullScan() {
14    console.log(`Starting full scan of park ${this.parkId}...`);
15    const results = await Promise.all([
16      this.checkAllFences(),
17      this.checkAllCameras()
18    ]);
19    return results;
20  }
21
22  async checkAllFences() {
23    // Simulate checking fences
24    await new Promise(res => setTimeout(res, 600));
25    return { fences: "all secure", checked: 24 };
26  }
27
28  async checkAllCameras() {
29    await new Promise(res => setTimeout(res, 400));
30    return { cameras: "all online", checked: 48 };
31  }
32}
33
34const monitor = new DinoMonitor("JP-001");
35monitor.runFullScan().then(results => console.log("Scan results:", results));

Detailed Class Example: Dinosaur Monitoring System

1class DinosaurMonitoringSystem {
2  constructor(sector) {
3    this.sector = sector;
4    this.active = false;
5    this.dinosaurs = [];
6  }
7
8  async initialize() {
9    try {
10      // Fetch dinosaur list in the sector
11      const lista = await fetchDinosaurListInSector(this.sector);
12      this.dinosaurs = lista;
13
14      // Initialize sensors
15      await this.initializeCzujniki();
16
17      this.active = true;
18      console.log(`System monitorowania sectora ${this.sector} zainicjalizowany`);
19
20      return true;
21    } catch (error) {
22      console.error(`Monitoring system initialization error: ${error.message}`);
23      return false;
24    }
25  }
26
27  async initializeCzujniki() {
28    // Initialize sensors for each dinosaur
29    const promises = this.dinosaurs.map(dino =>
30      initializeCzujnikDlaDinosaura(dino.id)
31    );
32
33    return Promise.all(promises);
34  }
35
36  async monitor(monitoringDuration = 60000) {
37    if (!this.active) {
38      throw new Error("System monitorowania nie jest active");
39    }
40
41    console.log(`Rozpoczynam monitorowanie sectora ${this.sector} for ${monitoringDuration/1000} sekund...`);
42
43    // Monitor for a specified time
44    const startTime = Date.now();
45
46    while (Date.now() - startTime < monitoringDuration) {
47      // Fetch status of all dinosaurs
48      const statuses = await Promise.all(
49        this.dinosaurs.map(dino => checkDinosaurStatus(dino.id))
50      );
51
52      // Analyze statuses
53      const abnormalDinosaurs = statuses.filter(s => s.status !== "normal");
54
55      if (abnormalDinosaurs.length > 0) {
56        console.warn(`Detected ${abnormalDinosaurs.length} dinosaurs with abnormal status!`);
57
58        for (const status of abnormalDinosaurs) {
59          console.warn(`Dinosaur #${status.dinoId}: ${status.status} - ${status.details}`);
60
61          // Send alert for each abnormal status
62          await sendAlert(this.sector, status);
63        }
64      }
65
66      // Wait 5 seconds before the next check
67      await new Promise(resolve => setTimeout(resolve, 5000));
68    }
69
70    console.log(`Monitoring of sector ${this.sector} completed.`);
71    return `Monitoring completed: ${new Date().toISOString()}`;
72  }
73}
74
75// Using the class
76async function startMonitoringSystem(sector) {
77  const system = new DinosaurMonitoringSystem(sector);
78
79  const initializationSuccessful = await system.initialize();
80  if (!initializationSuccessful) {
81    console.error(`Failed to initialize the system for sector ${sector}`);
82    return;
83  }
84
85  // Monitor for 5 minutes
86  await system.monitor(300000);
87
88  console.log(`Monitoring session for sector ${sector} completed`);
89}

Async IIFE (Immediately Invoked Function Expression)

Sometimes you need to run async code at the top level (outside any async function). Use an async IIFE:

1// Immediately runs the async function
2(async () => {
3  try {
4    const status = await getDinoStatus(7);
5    console.log("Status:", status);
6
7    const report = await prepareDinoReport(7);
8    console.log("Report ready for dinosaur:", report.dino.name);
9  } catch (error) {
10    console.error("Top-level error:", error.message);
11  }
12})();

Recursive Async Functions

Async functions can call themselves recursively:

1async function retryOperation(operation, maxRetries, delay = 500) {
2  for (let attempt = 1; attempt <= maxRetries; attempt++) {
3    try {
4      const result = await operation();
5      console.log(`Success on attempt ${attempt}`);
6      return result;
7    } catch (error) {
8      console.log(`Attempt ${attempt} failed: ${error.message}`);
9      if (attempt === maxRetries) throw error;
10      await new Promise(res => setTimeout(res, delay * attempt)); // Exponential-ish backoff
11    }
12  }
13}
14
15// Usage — retry sensor read up to 3 times
16async function readUnreliableSensor(sensorId) {
17  return retryOperation(
18    () => new Promise((resolve, reject) => {
19      setTimeout(() => {
20        Math.random() > 0.6
21          ? resolve({ sensorId, value: Math.random() * 100 })
22          : reject(new Error(`Sensor ${sensorId} did not respond`));
23      }, 300);
24    }),
25    3 // Max 3 attempts
26  );
27}
28
29readUnreliableSensor("THERMO-07")
30  .then(data => console.log("Final reading:", data.value.toFixed(1)))
31  .catch(error => console.error("All retries failed:", error.message));

Async Generators

Async generators combine generators with async operations — perfect for streaming data:

1async function* streamDinoUpdates(dinoIds, intervalMs) {
2  for (const id of dinoIds) {
3    // Simulate fetching live update for each dinosaur
4    await new Promise(res => setTimeout(res, intervalMs));
5    const update = await fetchDinosaurData(id);
6    yield { timestamp: new Date().toISOString(), dinosaur: update };
7  }
8}
9
10// Consuming the async generator
11async function monitorLive() {
12  const generator = streamDinoUpdates([1, 2, 3, 4, 5], 500);
13
14  for await (const update of generator) {
15    console.log(`[${update.timestamp}] ${update.dinosaur.name}: health ${update.dinosaur.health}%`);
16  }
17
18  console.log("Live monitoring cycle complete.");
19}
20
21monitorLive();

Common Mistakes to Avoid

1// ❌ Mistake 1: Using await without async
2function badFunction() {
3  const data = await fetchDinosaurData(1); // SyntaxError!
4}
5
6// ✅ Fix: Mark function as async
7async function goodFunction() {
8  const data = await fetchDinosaurData(1); // OK
9}
10
11// ❌ Mistake 2: Sequential awaits for independent operations (slow)
12async function slowReport(id) {
13  const a = await fetchDinosaurData(id);
14  const b = await fetchEnclosureStatus(a.enclosureId); // Only needs enclosureId
15  const c = await fetchVetReport(id); // Independent — but waits for b to finish!
16  return { a, b, c };
17}
18
19// ✅ Fix: Parallel where possible
20async function fastReport(id) {
21  const dino = await fetchDinosaurData(id);
22  const [enclosure, vetReport] = await Promise.all([
23    fetchEnclosureStatus(dino.enclosureId),
24    fetchVetReport(id) // Runs in parallel with enclosure fetch
25  ]);
26  return { dino, enclosure, vetReport };
27}
28
29// ❌ Mistake 3: Forgetting to await — fires and forgets
30async function badSave(data) {
31  saveToDatabase(data); // Not awaited — errors are silently swallowed!
32  console.log("Saved!"); // Prints before save completes
33}
34
35// ✅ Fix: Always await async operations whose result matters
36async function goodSave(data) {
37  await saveToDatabase(data);
38  console.log("Saved!");
39}
40
41function saveToDatabase(data) { return new Promise(res => setTimeout(res, 500)); }

Design Patterns with async/await

The async/await syntax opens new possibilities for design patterns in JavaScript. Here are a few useful patterns:

Retry Pattern

1async function withRetries(operation, options = {}) {
2  const { maxAttempts = 3, delay = 1000, backoffMultiplier = 2 } = options;
3
4  let lastError;
5
6  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
7    try {
8      if (attempt > 1) {
9        console.log(`Attempt ${attempt}/${maxAttempts}...`);
10      }
11
12      return await operation();
13    } catch (error) {
14      console.error(`Attempt ${attempt} failed: ${error.message}`);
15      lastError = error;
16
17      if (attempt < maxAttempts) {
18        const waitTime = delay * Math.pow(backoffMultiplier, attempt - 1);
19        console.log(`Waiting ${waitTime}ms before the next attempt...`);
20        await new Promise(resolve => setTimeout(resolve, waitTime));
21      }
22    }
23  }
24
25  throw new Error(`Operation failed after ${maxAttempts} attempts. Last error: ${lastError.message}`);
26}
27
28// Example usage
29async function connectToSecuritySystem() {
30  return withRetries(
31    async () => {
32      const connection = await establishSecuritySystemConnection();
33      const autentykacja = await authenticateConnection(connection);
34      return await fetchSecurityStatus(connection, autentykacja);
35    },
36    {
37      maxAttempts: 5,
38      delay: 2000,
39      backoffMultiplier: 1.5
40    }
41  );
42}

Timeout Pattern

1async function withTimeout(operation, timeLimit, message) {
2  // Create a Promise that rejects after the time limit
3  const timeoutPromise = new Promise((_, reject) => {
4    setTimeout(() => {
5      reject(new Error(`Time limit exceeded (${timeLimit}ms): ${message}`));
6    }, timeLimit);
7  });
8
9  // Race between the operation and the timeout
10  return Promise.race([operation(), timeoutPromise]);
11}
12
13// Example usage
14async function safeTemperatureReading(enclosureId) {
15  try {
16    const temperatura = await withTimeout(
17      () => readEnclosureTemperature(enclosureId),
18      5000,
19      `Reading temperature of enclosure #${enclosureId}`
20    );
21
22    console.log(`Enclosure #${enclosureId}: ${temperatura}°C`);
23    return temperatura;
24  } catch (error) {
25    console.error(`Temperature reading error: ${error.message}`);
26    return null; // Or we can return a default value
27  }
28}

Retry with Exponential Backoff

1async function withExponentialBackoff(operation, options = {}) {
2  const {
3    maxAttempts = 5,
4    baseWait = 1000,
5    maxWait = 30000,
6    jitter = true
7  } = options;
8
9  let attempt = 0;
10
11  while (true) {
12    try {
13      return await operation(++attempt);
14    } catch (error) {
15      if (attempt >= maxAttempts) {
16        throw new Error(`Operation failed after ${attempt} attemptch: ${error.message}`);
17      }
18
19      // Calculate wait time with exponential backoff
20      let waitTime = Math.min(
21        maxWait,
22        baseWait * Math.pow(2, attempt - 1)
23      );
24
25      // Add jitter (randomness) to the wait time
26      if (jitter) {
27        waitTime = waitTime * (0.5 + Math.random() * 0.5);
28      }
29
30      console.log(`Attempt ${attempt} failed. Waiting ${Math.round(waitTime)}ms before the next attempt...`);
31
32      await new Promise(resolve => setTimeout(resolve, waitTime));
33    }
34  }
35}
36
37// Example usage
38async function synchronizeDataWithCentral() {
39  return withExponentialBackoff(
40    async (attempt) => {
41      console.log(`Attempt synchronizacji #${attempt}...`);
42      const data = await fetchCurrentData();
43      await sendDataToCentral(data);
44      return `Synchronization completed: ${data.length} records`;
45    },
46    {
47      maxAttempts: 10,
48      baseWait: 2000,
49      maxWait: 60000
50    }
51  );
52}

Waterfall Pattern (Cascade Operations)

1async function cascadeSearch(query) {
2  console.log(`Starting cascade search for: ${query}`);
3
4  // Try the cache first
5  try {
6    console.log("Searching cache...");
7    const cacheResult = await searchCache(query);
8    console.log("Found in cache!");
9    return {
10      source: "cache",
11      result: cacheResult
12    };
13  } catch (error) {
14    console.log("Not found in cache, trying local database...");
15  }
16
17  // Then try the local database
18  try {
19    const localResult = await searchLocalDatabase(query);
20    console.log("Found in local database!");
21    // Update cache
22    await updateCache(query, localResult);
23    return {
24      source: "local_db",
25      result: localResult
26    };
27  } catch (error) {
28    console.log("Not found in local database, trying central database...");
29  }
30
31  // Finally try the central database
32  try {
33    const centralResult = await searchCentralDatabase(query);
34    console.log("Found in central database!");
35    // Update local database and cache
36    await updateLocalDatabase(query, centralResult);
37    await updateCache(query, centralResult);
38    return {
39      source: "central_db",
40      result: centralResult
41    };
42  } catch (error) {
43    throw new Error(`No results found for query: ${query}`);
44  }
45}

Summary

The async/await syntax offers an elegant and readable way of working with asynchronous code in JavaScript, built on top of Promises. Here are the main advantages:

  1. Readability — code looks more like synchronous code, making it easier to understand program flow
  2. Error handling — ability to use standard
    try/catch
    syntax instead of
    .catch()
  3. Debugging — easier stack trace tracking and breakpoint setting
  4. Composition — easier to compose complex asynchronous operations

However, keep a few important points in mind:

  1. Async functions always return a Promise — even if you don't use
    await
    inside the function
  2. Watch out for sequential execution — using
    await
    before each call causes sequential execution, which may not be optimal
  3. Don't forget error handling — always use
    try/catch
    or
    .catch()
    when calling async functions
  4. Use Promise.all() for parallel execution — when operations are independent and can run in parallel

In Jurassic Park, as in any complex JavaScript application, async/await is an invaluable tool for managing asynchronous operations, such as communicating with security systems, monitoring dinosaurs, or coordinating various park subsystems.

In the next exercise we will focus on error handling strategies in asynchronous code, which is crucial for creating resilient and reliable applications.

Go to CodeWorlds