In our previous exploration of asynchronous programming, we laid the foundation for understanding its importance in maintaining application responsiveness, particularly when dealing with operations like network requests, file I/O, or heavy computations. Now, we'll delve deeper into the practical implementation of asynchronous code in Dart, exploring the core concepts of Futures, Callbacks, and Async/Await.
Why Asynchronous Code Matters
Fetching data from a network: Downloading files, retrieving data from an API server, or making remote requests are all time-consuming processes that benefit from asynchronous handling.Writing to a database: Persisting data to a database involves interactions with external systems that can introduce delays. Asynchronous operations help prevent the main thread from being blocked during these operations.Reading data from a file: Reading large files can be resource-intensive. Asynchronous operations enable your program to process other tasks while the file reading happens in the background.
Unlocking the Power of Futures
Completed: The asynchronous operation has finished, and the Future holds either a value (if successful) or an error.Uncompleted: The asynchronous operation is still in progress.
String createOrderMessage() {
var order = fetchUserOrder();
return 'Your order is: $order';
}
Future<String> fetchUserOrder() {
return Future.delayed(
const Duration(seconds: 2),
() => 'Large Latte',
);
}
void main() {
print(createOrderMessage());
}
Your order is: Instance of 'Future<String>'
fetchUserOrder() is an asynchronous function that returns a Future<String> representing the eventual order. createOrderMessage() calls fetchUserOrder() but doesn't wait for it to finish. It immediately returns a string containing the Future object. print() in main() doesn't wait for the Future to complete, so it prints the Future object itself.
Navigating Futures with Callbacks
then(): This method executes a callback function when the Future successfully completes with a value.
void main() {
print('start fetching data');
Future<String>.delayed(Duration(seconds: 2), () {
return 'data are fetched';
}).then((value) {
print(value);
});
print('end fetching data');
}
start fetching data
end fetching data
data are fetched
catchError(): This method allows you to handle any errors that occur during the execution of the Future.
void main() {
print('start fetching data');
Future<String>.delayed(Duration(seconds: 2), () {
return 'data are fetched';
}).then((value) {
print(value);
}).catchError((error) {
print(error);
});
print('end fetching data');
}
start fetching data
end fetching data
data are fetched
Future<int> fetchNumber(bool succeed) {
return Future.delayed(Duration(seconds: 2), () {
if (succeed) {
return 42;
} else {
throw Exception("Failed to fetch number");
}
});
}
void main() {
fetchNumber(false).then((value) {
print("The number is $value");
}).catchError((error) {
print("Error occurred: $error");
});
print("Waiting for the number...");
}
Waiting for the number...
Error occurred: Exception: Failed to fetch number
whenComplete(): This method executes a callback function regardless of whether the Future completes successfully or with an error.
Future<int> fetchNumber(bool succeed) {
return Future.delayed(Duration(seconds: 2), () {
if (succeed){
return 42;
} else {
throw Exception("Failed to fetch number");
}
});
}
void main() {
fetchData(false).then((value) {
print(value);
}).catchError((error) {
print("Error occurred: $error");
}).whenComplete(() {
print("Operation complete, cleaning up...");
});
print("Waiting for the operation to complete...");
}
Waiting for the operation to complete...
Error occurred: Exception: Failed to fetch number
Operation complete, cleaning up...
Embracing Async/Await: A More Declarative Approach
async: Marks a function as asynchronous, allowing it to use await.await: Pauses execution within an async function until the Future it's applied to completes, then returns the value or error.
import 'dart:async';
Future<String> fetchData() async {
await Future.delayed(Duration(seconds: 2));
return 'Data fetched successfully!';
}
Future<void> main() async {
print('Fetching data...');
String result = await fetchData();
print(result);
print('Done fetching data.');
}
Fetching data...
Data fetched successfully!
Done fetching data.
fetchData() is an async function that simulates fetching data and introduces a 2-second delay. await before Future.delayed pauses execution until the delay completes. main() is also an async function because it uses await before fetchData(). Execution pauses in main() until fetchData() returns its result.
Graceful Error Handling in Asynchronous Functions
Future<void> printOrderMessage() async {
try {
print('Awaiting user order...');
var order = await fetchUserOrder();
print(order);
} catch (err) {
print('Caught error: $err');
}
}
Future<String> fetchUserOrder() {
// Imagine that this function is more complex.
var str = Future.delayed( const Duration(seconds: 4), () {
throw 'Cannot locate user order';
});
return str;
}
void main() async {
await printOrderMessage();
}
Awaiting user order...
Caught error: Cannot locate user order
Future<void> printOrderMessage() {
print('Awaiting user order...');
return fetchUserOrder().then((order) {
print(order);
}).catchError((err) {
print('Caught error: $err');
});
}
Future<String> fetchUserOrder() {
// Imagine that this function is more complex.
return Future.delayed(const Duration(seconds: 4), () {
throw 'Cannot locate user order';
});
}
void main() {
printOrderMessage();
}
Awaiting user order...
Caught error: Cannot locate user order
Readability: async/await syntax is generally considered more readable and intuitive, especially when dealing with complex asynchronous operations. Callback chains can become cumbersome with nested calls.Error Handling: try-catch blocks within async functions offer a familiar and straightforward approach to error handling, similar to synchronous code. Callback methods like catchError() can sometimes be less intuitive.
Putting It All Together: Real-World Example
import 'package:http/http.dart' as http;
import 'dart:convert';
void fetchDataUsingCallbacks() {
final url = Uri.parse('https://jsonplaceholder.typicode.com/posts/1');
http.get(url).then((response) {
if (response.statusCode == 200) {
var data = jsonDecode(response.body);
print('Data fetched successfully: ${data['title']}');
} else {
print('Failed to load data');
}
}).catchError((error) {
print('An error occurred: $error');
}).whenComplete(() {
print('Request complete.');
});
}
void main() {
fetchDataUsingCallbacks();
}
Data fetched successfully: sunt aut facere repellat provident occaecati excepturi optio reprehenderit
Request complete.
An error occurred: <error message>
Request complete.
import 'package:http/http.dart' as http;
import 'dart:convert';
Future<void> fetchDataUsingAsyncAwait() async {
final url = Uri.parse('https://jsonplaceholder.typicode.com/posts/1');
try {
final response = await http.get(url);
if (response.statusCode == 200) {
var data = jsonDecode(response.body);
print('Data fetched successfully: ${data['title']}');
} else {
print('Failed to load data');
}
} catch (error) {
print('An error occurred: $error');
} finally {
print('Request complete.');
}
}
void main() async {
await fetchDataUsingAsyncAwait();
}
Data fetched successfully: sunt aut facere repellat provident occaecati excepturi optio reprehenderit
Request complete.
An error occurred: <error message>
Request complete.
0 comments:
Post a Comment