Async programming in Dart & JS

This blog post provides an overview of asynchronous programming, focusing on the concepts and techniques used in Dart and JavaScript. The blog further compares and contrasts asynchronous programming in Dart and JavaScript, discussing their similarities and differences.

GraphQL has a role beyond API Query Language- being the backbone of application Integration
background Coditation

Async programming in Dart & JS

Asynchronous Programming:

Asynchronous programming is a technique that enables your program to start a potentially long-running task and still be able to be responsive to other events while that task runs, rather than having to wait until that task has finished. Once that task has finished, your program is presented with the result.
An asynchronous model allows multiple things to happen at the same time. When you start an action, your program continues to run. When the action finishes, the program is informed and gets access to the result (for example, the data read from disk or api call).
Computers are asynchronous by design

Asynchronous Programming In Dart

Dart futures run on the event loop, which manages the event queue. As Dart is a single-threaded program just like JS, all the tasks you write end up in the event loop for execution.

Asynchronous operations in Dart can be achieved in two ways:

  1. Using await and async
  2. Using Future API.

Future:

One of the basic implementations of asynchronous coding patterns in Dart is Future. If you are already familiar with JavaScript, Future has the exact same concept with Promise.
Instead of blocking all computation until the result is available, the asynchronous computation immediately returns a Future which will eventually "complete" with the result.

So a future can be in one of 3 states:

Uncompleted: The uncompleted future is waiting for the function’s asynchronous activity to complete or to throw a mistake.

Completed with a value: The completed Future (data) is ready

Completed with an error:  The completed Future with Error/Mistake data

The Future class has a few constructors:

  • Future.any(): 

Returns the result of the first future in [futures] to complete.
It takes the list of futures and return the result of the first future in the futures list to report that it is complete


Future<T> any<T>(Iterable<Future<T>> futures) 
Type: Future<Object> Function(Iterable<Future<Object>>)

The returned future is completed with the result of the first future in [futures] to report that it is complete, whether it's with a value or an error. The results of all the other futures are discarded.
If [futures] is empty, or if none of its futures complete, the returned future never completes.


Future<int> slowInt() async { 
 await Future.delayed(const Duration(seconds: 2)); 
 return 2; 
}

Future<String> delayedString() async { 
 await Future.delayed(const Duration(seconds: 4)); 
 throw Exception('Time has passed'); 
} 

Future<int> fastInt() async { 
 await Future.delayed(const Duration(seconds: 1)); 
 return 3; 
}


void main() async {
final result = await Future.any([slowInt(), delayedString(), fastInt()]);
// The future of fastInt completes first, others are ignored.
 print('Result: $result'); // 3 
}


Output:
Result: 3

  • Future.delayed(): 

Creates a future that runs its computation after a delay.
The [computation] will be executed after the given [duration] has passed, and the future is completed with the result of the computation.
By default the return type of the delayed() is dynamic and If [computation] is omitted, it will be treated as if [computation] was () => null, and the future will eventually complete with the null value. In that case, [T] must be nullable.

Example: 

In this example Future will wait for 2 seconds then it will execute the computation part and message printed after 2 seconds of delay.
If the duration is 0 or less, it completes no sooner than in the next event-loop iteration, after all microtasks have run.


void main() async {
  Future.delayed(Duration(seconds: 2), 
    () {
        print('This Message will print after 2 seconds')
     }
  )
}


Output:
This Message will print after 2 seconds

If the duration is 0 or less, it completes no sooner than in the next event-loop iteration, after all microtasks have run.
Note: Future will complete once you use async/await or .then(), it won’t complete without it

The Future API and callbacks

Functions that use the Future API register callbacks that handle the value (or the error) that completes a Future. For example:

myFunc().then(processValue).catchError(handleError);

The registered callbacks fire based on the following rules: then()’s callback fires if it is invoked on a Future that completes with a value; catchError()’s callback fires if it is invoked on a Future that completes with an error.

In the example above, if myFunc()’s Future completes with a value, then()’s callback fires. If no new error is produced within then(), catchError()’s callback does not fire. On the other hand, if myFunc() completes with an error, then()’s callback does not fire, and catchError()’s callback does.

.then() : 

Register callbacks to be called when this future completes.

.whenComplete() : 

Registers a function to be called when this future completes.

The [action] function is called when this future completes, whether it does so with a value or with an error.

This is the asynchronous equivalent of a "finally" block.

.onError():

Handles errors emitted by this Future.

This is the asynchronous equivalent of a "catch" block.

Returns a new Future that will be completed with either the result of this future or the result of calling the onError callback.

Using Async/Await:

Most programming languages have async and await in their syntax. Basically, there is just an alternate syntax to handle asynchronous code like Future and Streams to make it looks cleaner and readable.

The await and async keywords are used together. The function that is supposed to be doing the expensive work is marked with keyword async. Inside the function, the expensive call is prefixed by keyword await

Let's see in the following code snippet how async and await keywords are used. await can only be called in a function which is marked/declared as async. Future keyword before the function getLanguage() means that this function will be executing asynchronously and will be suspended when come across await.

Sequencing Function Calls

It's possible to control the order of execution of asynchronous functions using sequencing with help of await and async. Let's see how this is done in the example below. In this example, there are three functions getDataA(), getDataB(), and getDataC(). We want them to execute one after another. The await keyword does exactly that. It executes the function synchronously until it comes across await, and waits there for it to finish before executing the next line, in this case another await call.

Error Handling (Try/Catch Block)

Let’s see how to handle exceptions thrown in the long running asynchronous operations. When the await keyword is used, an asynchronous call behaves like a synchronous one. In such cases, error handling for asynchronous and synchronous code is handled in a similar way.

The asynchronous call prefixed with await keyword is wrapped in a try/catch block. When an exception is thrown in the try block, the catch block executes its code.


Asynchronous Programming in JavaScript: 

In general JavaScript is running code in a non-blocking way. This means that code which is taking some time to finish (like accessing an API, reading content from the local file system etc.) is being executed in the background and in parallel the code execution is continued. This behavior is described by the term asynchronous programming. Because JavaScript is executed in that non-blocking way you have to take additional measures to deal with that code if you need to have the result available before further code is being executed.
Let’s take a look at the following first example to get a better understanding of the problem:


const getUser = () => {
  setTimeout(() => {
   return { name: 'John Doe', age: 25 }
  }, 5000)
}
const user = getUser()
console.log(user.name)

Here we’re defining a function getUser. This function is returning a user data (as a JSON object with the string property name and age). To simulate an API call we’re delaying the response for 5 seconds by wrapping the return statement in an anonymous function which is passed to setTimeout. The second parameter of setTimeout is the amount of milliseconds for which the execution of the function should be delayed.

The result of the getUser function call is stored in a constant named user and finally we’re trying to print out the name information by using a console.log call. This result in the following error when executing this code:

So why are we getting this type of error stating that it cannot read property name of undefined. The reason is quite clear: as the return value of getUser() function is delivered with a delay of 5000 milliseconds the object is not available when trying to output user.name to the console. The code execution has continued without waiting for the call of getUser to be finished. This is a typical problem when dealing with asynchronous code executing. So what could be a solution to this problem? Fortunately JavaScript has many solutions to offer. First we’re going to take a look at Callbacks in the following section.

What are Callbacks in JavaScript?

A callback is a function that is passed inside another function, and then called in that function to perform a task.

Let’s implement a callback to our previous example.

From the result you can learn that the last console.log statement is executed first. The reason is quite obvious. The second console.log statement is embedded in the callback function of getUser. The execution of this callback function is still delayed by 5000 milliseconds, so the output to the command line is delayed as well.

Alternatives to callbacks:

Starting with ES6, JavaScript introduced several features that help us with asynchronous code that do not involve using callbacks: Promises (ES6) and Async/Await (ES2017).

JavaScript  Promises:

Promises are used to handle asynchronous operations in JavaScript. They are easy to manage when dealing with multiple asynchronous operations where callbacks can create callback hell leading to unmanageable code.

A Promise is in one of these states:

  • pending: initial state, neither fulfilled nor rejected.
  • fulfilled: meaning that the operation was completed successfully.
  • rejected: meaning that the operation failed.
  • settled: Promise has been fulfilled or rejected.

Promises came along to solve the problems of callback functions. A promise takes in two functions as parameters. That is, resolve and reject. Remember that resolve is success, and reject is for when an error occurs.

In the above example we have created a function named canDriveCar which accepts a parameter which is of type user and it returns the promise data.
Inside the function we have returned a Promise and inside the promise we performed some computation and then returned the resolve or reject object of promise with a certain value in it  based on condition fulfillment.
Later we called the canDriveCar function with two user objects named user1 and user2 which have name and age property inside it. canDriveCar function returns a promise object so we can not read the promise value directly, to read promise value we have to use either chain .then() function or async/await keyword.
So in above example we have use .then(), as we know the promise can have 3 states; pending, fulfilled, rejected. It then handles the fulfilled state of promise while .catch() handles the rejected state of promise. We have covered these two methods in the above example and you can see the output for the same.
The finally() method of a Promise object schedules a function to be called when the promise is settled (either fulfilled or rejected). It immediately returns an equivalent Promise object, allowing you to chain calls to other promise methods.

Example with finally(): 


Sequencing Function Calls In JS

In the above example we have nested/chained multiple promises to run the code in sequence.
The thing is, chaining promises together just like callbacks can get pretty bulky and confusing. That's why async and await was brought about
Creating an async function:

Note: that calling an async function will always return a Promise. Take a look at this:

As you can see in the above example output,  we have created an Async function named fetchUserAsync and return the user object from that function. We have used the async keyword with the function signature which makes fetchUserAsync to return the promise.
We are calling the fetchUserAsync function inside the printResult method and storing the result of that function in the result variable, printing the result in the console. First console value is a promise and the second value is undefined.
To read the result of the fetchUserAsync function we have to use await keyword before it so that we can get the result of the promise.

Here is the same example with await keyword:

As you can see in the above example, we have marked the printResult function as async so that i can use await keyword inside the function, now the output value is user object, and we can access the property of user object resolved by the promise result. 

Hi, I am Manish Dube. I am a Javascript & Flutter developer with over 6 years of experience in software development. In my free time, I enjoy playing outdoor games and staying up-to-date with the latest developments in the tech industry.

Want to receive update about our upcoming podcast?

Thanks for joining our newsletter.
Oops! Something went wrong.

Latest Articles

Implementing Custom Instrumentation for Application Performance Monitoring (APM) Using OpenTelemetry

Application Performance Monitoring (APM) has become crucial for businesses to ensure optimal software performance and user experience. As applications grow more complex and distributed, the need for comprehensive monitoring solutions has never been greater. OpenTelemetry has emerged as a powerful, vendor-neutral framework for instrumenting, generating, collecting, and exporting telemetry data. This article explores how to implement custom instrumentation using OpenTelemetry for effective APM.

Mobile Engineering
time
5
 min read

Implementing Custom Evaluation Metrics in LangChain for Measuring AI Agent Performance

As AI and language models continue to advance at breakneck speed, the need to accurately gauge AI agent performance has never been more critical. LangChain, a go-to framework for building language model applications, comes equipped with its own set of evaluation tools. However, these off-the-shelf solutions often fall short when dealing with the intricacies of specialized AI applications. This article dives into the world of custom evaluation metrics in LangChain, showing you how to craft bespoke measures that truly capture the essence of your AI agent's performance.

AI/ML
time
5
 min read

Enhancing Quality Control with AI: Smarter Defect Detection in Manufacturing

In today's competitive manufacturing landscape, quality control is paramount. Traditional methods often struggle to maintain optimal standards. However, the integration of Artificial Intelligence (AI) is revolutionizing this domain. This article delves into the transformative impact of AI on quality control in manufacturing, highlighting specific use cases and their underlying architectures.

AI/ML
time
5
 min read