Async Await Example

In Dart programming, asynchronous operations are commonly used to handle tasks that may take a significant amount of time to complete, such as fetching data from a server or reading/writing files. The async and await keywords are essential tools for managing asynchronous code in a synchronous manner, making it easier to work with Future objects and improve code readability.

What is Async Await?

In Dart, async is used to mark a function as asynchronous, allowing it to use the await keyword to pause execution until a Future is complete. By using async and await, developers can write asynchronous code that looks and behaves more like synchronous code, making it easier to manage complex asynchronous operations.

History/Background

The async and await keywords were introduced in Dart 2.0 as part of the language's ongoing efforts to improve support for asynchronous programming. These keywords simplify the syntax for working with asynchronous operations and make it easier for developers to write and maintain asynchronous code in Dart.

Syntax

Example

Future<void> fetchData() async {
  // asynchronous operation
}

void main() async {
  await fetchData();
}
  • async: Marks a function as asynchronous, allowing the use of await within the function body.
  • await: Pauses the execution of a function until the awaited Future is complete.
  • Future<void>: Specifies the return type of an asynchronous function, indicating that it will eventually return a void value.
  • Key Features

  • Simplifies asynchronous programming by making it more readable and synchronous-like.
  • Allows developers to write non-blocking code that efficiently handles long-running tasks.
  • Improves error handling by enabling the use of try-catch blocks within asynchronous functions.
  • Example 1: Basic Usage

    Example
    
    Future<String> fetchData() async {
      await Future.delayed(Duration(seconds: 2));
      return "Data fetched successfully!";
    }
    
    void main() async {
      print("Fetching data...");
      String data = await fetchData();
      print(data);
    }
    

Output:

Output

Fetching data...
Data fetched successfully!

Example 2: Error Handling

Example

Future<void> simulateError() async {
  await Future.delayed(Duration(seconds: 2));
  throw Exception("Error occurred!");
}

void main() async {
  try {
    print("Simulating error...");
    await simulateError();
  } catch (e) {
    print("Error: $e");
  }
}

Output:

Output

Simulating error...
Error: Exception: Error occurred!

Common Mistakes to Avoid

1. Ignoring the `await` keyword

Problem: Beginners often forget to use the await keyword when calling asynchronous functions, leading to unexpected behavior because the function returns a Future instead of the actual value.

Example

// BAD - Don't do this
Future<String> fetchData() async {
  return "Data fetched";
}

void main() {
  String data = fetchData(); // Missing await
  print(data); // This will print an instance of Future<String>
}

Solution:

Example

// GOOD - Do this instead
Future<String> fetchData() async {
  return "Data fetched";
}

void main() async {
  String data = await fetchData(); // Correctly using await
  print(data); // This will print "Data fetched"
}

Why: Not using await means you're working with a Future object rather than the resolved value. Always use await when dealing with asynchronous calls to ensure the method completes before you use its result.

2. Forgetting to mark the function as `async`

Problem: If a function that uses await is not marked as async, it will cause a compilation error.

Example

// BAD - Don't do this
void fetchAndPrintData() {
  String data = await fetchData(); // await used in a non-async function
  print(data);
}

Solution:

Example

// GOOD - Do this instead
Future<void> fetchAndPrintData() async {
  String data = await fetchData(); // Correctly inside an async function
  print(data);
}

Why: The await keyword can only be used within asynchronous functions. Marking a function as async allows you to use await, making your code cleaner and more readable.

3. Not handling errors in asynchronous calls

Problem: Beginners might neglect to handle errors that can arise from asynchronous operations, resulting in unhandled exceptions.

Example

// BAD - Don't do this
Future<String> fetchData() async {
  throw Exception("Failed to fetch data");
}

void main() async {
  String data = await fetchData(); // No error handling
  print(data); // This will throw an exception
}

Solution:

Example

// GOOD - Do this instead
Future<String> fetchData() async {
  throw Exception("Failed to fetch data");
}

void main() async {
  try {
    String data = await fetchData();
    print(data);
  } catch (e) {
    print("Error: $e"); // Proper error handling
  }
}

Why: Not handling errors can lead to crashes. Always use try-catch blocks around your asynchronous calls to manage exceptions gracefully.

4. Chaining multiple async calls without handling their order

Problem: Beginners might call multiple asynchronous functions sequentially without understanding the implications of their execution order.

Example

// BAD - Don't do this
Future<void> fetchData1() async {
  // Simulating a delay
  await Future.delayed(Duration(seconds: 2));
  print("Data 1 fetched");
}

Future<void> fetchData2() async {
  await Future.delayed(Duration(seconds: 1));
  print("Data 2 fetched");
}

void main() async {
  fetchData1();
  fetchData2(); // This will run in parallel, not waiting for fetchData1
}

Solution:

Example

// GOOD - Do this instead
Future<void> fetchData1() async {
  await Future.delayed(Duration(seconds: 2));
  print("Data 1 fetched");
}

Future<void> fetchData2() async {
  await Future.delayed(Duration(seconds: 1));
  print("Data 2 fetched");
}

void main() async {
  await fetchData1(); // Wait for fetchData1 to complete
  await fetchData2(); // Then call fetchData2
}

Why: Calling asynchronous functions in sequence without await can lead to unexpected results. Use await to ensure that your functions execute in the intended order.

5. Using `async` unnecessarily

Problem: Some beginners may mark functions as async even when they don't use await, which can create confusion.

Example

// BAD - Don't do this
Future<void> doNothing() async {
  // No await used here
}

// This function is not actually asynchronous

Solution:

Example

// GOOD - Do this instead
void doNothing() {
  // No need for async here
}

Why: Using async unnecessarily can make code harder to read and understand. Only mark a function as async when it truly involves asynchronous operations.

Best Practices

1. Always Use `await` for Asynchronous Calls

Using await ensures that your code executes in a linear, predictable manner. It prevents the common pitfall of working with Future objects directly.

2. Handle Errors Gracefully

Always wrap your asynchronous calls in try-catch blocks. This is crucial for maintaining application stability and providing feedback to users when things go wrong.

3. Use `async` Only When Necessary

Mark functions as async only when they actually contain await calls. This keeps your code clean and avoids confusion about which functions are asynchronous.

4. Avoid Blocking the Main Thread

If you have heavy computations, ensure they are processed asynchronously. This keeps your app responsive and prevents UI freezes. Use Isolate for truly heavy tasks.

5. Keep Your Code Readable

Structure your asynchronous code in a way that makes the flow easy to follow. Use descriptive function names and comments to clarify the purpose of each async call.

6. Use `Future.wait` for Concurrent Calls

When you have multiple independent asynchronous calls, you can execute them concurrently using Future.wait, which can improve performance.

Example

void main() async {
  await Future.wait([fetchData1(), fetchData2()]);
  print("Both data fetched");
}

Key Points

Point Description
Use await for Asynchronous Results Ensure that you wait for Future results to avoid working with unresolved Future instances.
Mark Functions as async Only use the async keyword on functions that contain await expressions.
Implement Error Handling Always wrap asynchronous code in try-catch blocks to manage exceptions effectively.
Control Execution Order Use await to maintain the order of execution for dependent asynchronous calls.
Avoid Unnecessary async Only declare functions as async when they are performing asynchronous operations.
Enhance Responsiveness Ensure that heavy computations are handled in a way that doesn't block the UI thread, using Isolate if necessary.
Use Future.wait for Concurrency Leverage Future.wait to run multiple asynchronous operations in parallel when they do not depend on each other.
Prioritize Code Readability Write clear and maintainable asynchronous code by using descriptive naming conventions and structuring your code logically.

Input Required

This code uses input(). Please provide values below: