JavaScript Essentials for React Native - #5 Promises, async, await
Promises facilitate efficient handling of multiple tasks asynchronously
When we are building software, we need to make sure we do the following mentioned things smoothly :
Data Fetching
Image Loading
Push Notifications
Animations
Database Operations
Managing sensors
Dealing with third party APIs etc
And a lot of times, we need to manage seamless running of these tasks or combination of these tasks as we have "promised" our users of a great user experience.
To seamlessly execute these diverse tasks and ensure a consistent user experience, developers rely on a powerful JavaScript feature called Promises.
Why do we need Promises? Can we skip them?
As highlighted above, today the mobile applications have become very complex. Let's say you have to do 10 tasks : Task A, Task B ... Task J in your application. Here are the constraints:
Some of these tasks are dependent on each other, others independent.
Some of these tasks have more priority than others.
These tasks execute with different time on different mobile phones due to diverse range of mobile phone models in the market.
And as a software developer, you have to make sure that the software runs as you predicted, in the same way across all devices.
If you write JavaScript code without Promises i.e synchronous code, then it means you leave it to the constraints and the device and third party tasks, to decide the behavior of your application. Meaning unpredictable behavior.
Promises make software behavior more predictable, ensuring it behaves as intended.
Promise syntax
let promise = new Promise(function(resolve, reject) {
// executor OR producing code => This code does something and takes time
});
The function passed to
new Promise
is called the executor.When a new Promise is created, the executor runs automatically.
It contains the producing code responsible for generating the result.
The executor's arguments,
resolve
andreject
, are callbacks provided by JavaScript.When the executor obtains the result (regardless of timing), it should call one of these callbacks:
resolve(value)
— if the job is finished successfully, providing the result value.reject(error)
— if an error occurs, with the error object.
The executor runs automatically, attempting to perform a job.
- Upon completion, it calls
resolve
for success orreject
for an error.
- Upon completion, it calls
The promise object returned by the
new Promise
constructor has internal properties:state
— initially "pending," then changes to either "fulfilled" or "rejected."result
— initially undefined, changing to the value or error upon resolution.
Example of resolved promise
let promise = new Promise(function(resolve, reject) { // the function is executed automatically when the promise is constructed // after 1 second signal that the job is done with the result "done" setTimeout(() => resolve("done"), 1000); });
Example of rejected promise
let promise = new Promise(function(resolve, reject) { // after 1 second signal that the job is finished with an error setTimeout(() => reject(new Error("Whoops!")), 1000); });
Consumers: then, catch
Promise is just a wrapper around our unpredictable task ( doing API calls, file handling etc ).
Now, either that task was completed or failed, this info is communicated to our Promise by resolve or reject functions.
And we have special functions then, catch which are consumers of our promise.
then ( handle success, error events )
Syntax:
promise.then( function(result) { /* handle a successful result */ }, function(error) { /* handle an error */ } );
catch ( to handle errors only )
Syntax:
let promise = new Promise((resolve, reject) => { setTimeout(() => reject(new Error("Oops!")), 1000); }); // .catch(f) is the same as promise.then(null, f) promise.catch(alert); // shows "Error: Oops!" after 1 second
finally ( clean up )
Syntax:
new Promise((resolve, reject) => { /* do something that takes time, and then call resolve or maybe reject */ }) // runs when the promise is settled, doesn't matter successfully or not .finally(() => stop loading indicator)
Async-await
This is the modern way to work with promises.
await
only works inside anasync
function.A function that returns a promise is an
async
function. Meaning just adding this keyword outside any function, ensures that the function returns a promise.Example:
function greet() { // 1. normal function, returns "Hello" return "Hello" } async function greet2() { // 2. async function returns Promise return "Hello" } function greet3() { // 3. This is same as 2 return Promise.resolve("Hello"); }
await keyword works only inside async functions
let result = await promise;
await keyword makes JavaScript wait until that promise is settled
async function foo() { let promise = new Promise((resolve, reject) => { setTimeout(() => resolve("done!"), 1000) }); let result = await promise; // wait until the promise resolves console.log(result); // "done!" } foo();
add
try,
catch
,finally
blocks for error handling
Summary
In this blog, we talked about how we can make our apps more predictable and efficient using Promises. By breaking down complex tasks into small units and using Promises, developers can ensure a smoother and more reliable user experience across various scenarios.
Thanks for reading!