Asynchronous Programming in Dart
FARINU TAIWO
Posted on April 11, 2023
Introduction
I wrote this article based on my interest in asynchronous tasks. Asynchronous programming is important for developers because it allows their applications to perform multiple tasks at the same time, without one task blocking the others. For example, when an application needs to wait for data from a server, asynchronous programming allows it to continue running other tasks, rather than waiting for the data before proceeding. This results in a smoother and more responsive user experience for the application's users.
Synchronous And Asynchronous Programming
Explaining to a kid, synchronous programming means doing things one at a time, step by step, and waiting for each step to finish before moving on to the next one. It's like doing your homework one page at a time and not moving to the next page until you finish the one you're working on.
On the other hand, asynchronous programming means doing things at the same time, without waiting for each step to finish before starting the next one. It's like listening to music and drawing a picture at the same time, without having to stop one activity to do the other.
In Dart, we can write programs that work synchronously or asynchronously, depending on what we need to do. Synchronous programming is good for simple tasks that can be done quickly, while asynchronous programming is better for tasks that take a long time or need to run in the background while we do something else.
So, when we write a Dart program, we can choose whether to do things one at a time (synchronously) or things at the same time (asynchronously), depending on what we want to achieve.
What is Future?
Future is used in a dart to denote an operation that is to be completed or not completed. If the task is completed, we get a result otherwise, we return an error. In asynchronous programming, we are forcing the long-processed task to follow an ordered path.
void main() {
print('Fetching user data...');
var userData = fetchUserData();
print('User data received: $userData');
print('End of main');
}
Future<String> fetchUserData() {
// Imagine that this function is more complex and slow.
return Future.delayed(
Duration(seconds: 3),
() => 'John Doe'
); // simulate network delay
}
Output:
Fetching user data...
User data received: Instance of '_Future<String>'
End of main
From the code above, The fetchUserData()
function returns a Future
object that will be completed with the result of the network request after a delay of 3 seconds, represented by the Future.delayed()
method. In this case, the result is simply a string containing the name 'John Doe'.
The output shows that the main
function is executed synchronously, printing the first message to the console.
Next, the fetchUserData()
function is called, which returns a Future<String>
object. The returned future is assigned to the userData
variable.
The second message is then printed to the console, which shows the value of the userData
variable. Since userData
contains a Future<String>
object, the output displays "Instance of '_Future'" instead of the actual user data.
Finally, the last message is printed to the console, indicating the end of the main function.
The reason for this output is that the fetchUserData()
function returns a Future<String>
object, which represents the eventual result of the asynchronous operation. When we assign this future object to a variable and try to print it immediately, we see the type of the object instead of its value.
To access the value of the Future
object, we need to use the .then()
method or the await
keyword in an async
function.
Using .then() method
The .then()
method is used to register a callback function that will be called when a Future
object completes.
When we have a Future
object representing an asynchronous operation that will complete at some point in the future, we can use the .then()
method to attach a callback function that will be executed when the future is resolved with a result.
void main() {
print('Fetching user data...');
fetchUserData().then((userData) {
print('User data received: $userData');
print('End of main');
});
}
Output:
Fetching user data...
User data received: John Doe
End of main
In this example, we use the .then()
method to register a callback function to be called when the Future returned by fetchUserData()
is completed. The callback function takes the userData
string as its argument, which is then printed to the console along with another message indicating the end of the main function.
Note that in the example above, the print()
statement after the fetchUserData()
call has been removed, because we are now using the .then()
method to access the result of the Future
instead of assigning it to a variable and printing it directly.
What if we place print()
after the fetchUserData()
call?
void main() {
print('Fetching user data...');
fetchUserData().then((userData) {
print('User data received: $userData');
});
print('End of main');
}
Output:
Fetching user data...
End of main
User data received: John Doe
The third message ("End of main") is printed before the Future
is completed, because the call to fetchUserData()
returns a Future
immediately, and the .then()
method is used to register a callback function that will be executed later, when the Future
is complete.
This example shows how we can use the .then()
method to perform some action with the result of an asynchronous operation once it becomes available, without blocking the main thread.
For an ordered execution, you need to place the print inside the .then()
callback after userData
is retrieved.
Using async/await
The async
and await
keywords are used to write asynchronous code that looks synchronous and is easier to read and understand, without blocking the main thread.
void main() async {
print('Fetching user data...');
var userData = await fetchUserData();
print('User data received: $userData');
print('End of main');
}
Output:
Fetching user data...
End of main
User data received: John Doe
The async
keyword is used to mark the main
function as asynchronous, which allows us to use the await
keyword inside the function to pause its execution until a Future
is completed with a result.
In this case, when the await keyword is used with the fetchUserData()
function call, it pauses the execution of the main
function until the Future returned by fetchUserData()
is resolved with a result.
Once the Future
is completed, the await
keyword assigns the result to the userData
variable, and the execution of the main
function resumes, printing the second message to the console.
Using async
and await
in this way makes it easier to write and read asynchronous code, because it looks similar to synchronous code, and makes it clear what the flow of the code is. It also makes it easier to handle errors and exceptions that might be thrown by asynchronous operations, because they can be caught using a try-catch
block like in synchronous code.
Conclusion
In Dart, asynchronous programming can be achieved using Future
objects, which represent a value that may not be available yet, and the async
and await
keywords, which allow us to write asynchronous code that looks synchronous and is easier to read and understand.
Posted on April 11, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.