Once you learn about Promises and async/await You will write better code in no time

Feb 29, 2020

I used to hate JavaScript quite passionately. And I still think JavaScript, especially node has done a lot of terrible mistakes.

For example, file handling and IO was not what browsers initially to implemented. File and IO APIs still sucks in node. Streams and Buffers are still unintuitive and many seemingly simple tasks in node are done in terrible, almost hacky ways.

That being said, one thing that made JavaScript stand out from the rest is Google's V8 JavaScript engine. It includes tricks such as JIT compilation, hot functions compile to native code and a well written pure javascript algorithm can achieve over 90% of native performance.

So, we have now established that JavaScript can be fast. But usually, due to poor programming practices we've been following, it isn't.

Why your code isn't working as fast as it could be

  1. You haven't learned about ES6 features such as Promises and async/await
  2. It's 2020, learn about Promises and async/await now. Node and Browsers support it and Babel is your friend.
  3. You still use callbacks, leading to severe frustration.
  4. You use older, unmaintained packages, which didn't take advantage of newer developments of JavaScript.

What are Promises?

The Promise API in JS is perhaps the only redeeming factor for all the crappy APIs that node has provided us.

The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value. - MDN

If you still can't wrap your head around the asynchronous thingy, read on.

Let's imagine you have 3 things to do. Write an essay, do the dishes and wash your clothes. Here is the classic way you are used to do it.

1. write essay - 30 minutes
2. put the dishes into the dishwasher, wait for it to finish - 10 minutes
3. Put the clothes into the washing machine, wait for it to finish - 20 minutes

Now, You can imagine, while you were waiting for the dish washer and washing machine to do its job, you did nothing. Doing dishes and washing clothes are IO operations. We can definitely see an improvement by doing it the following way

1. Put the dishes into the dishwasher - 2 minutes
2. Put the clothes into the washing machine - 3 minutes
3. Write the essay for 5 minutes and dishwasher is done. (total 10 minutes)
4. Keep working on the essay for 17 minutes and washing machine is done. (total 20 minutes)
5. By the time you finish writing the essay, your dishes and clothes are clean. 30 minutes

This is a perfect analogy of asynchronous operations. You want to minimize idle time. This is different from parallel processing, where there are three people, each doing one of the activities. Asynchronous operations work on one thread and minimizes idle time.

Coming back to promises, the action of putting things into the dishwasher or washing machine, you had the idea that these activities will be completed in the future (that's what Dart calls Promises, btw).

When the dishwasher or washing machine beeps, your Promise is resolved (i.e. it's finished). If the washing machine broke down, your Promise would be rejected.

Refresher on writing Promises.

Let's code the above sentence in Promise based syntax.

function washClothes(){
    return new Promise((resolve, reject) => {
        let washingMachine = new WashingMachine();
        let clothesNeedToBeWashed = [];
        for(let cloth of clothes){
            if cloth.isDirty(){
                clothesNeedToBeWashed.push(cloth)
            }
        }
    	washingMachine.feedIn(clothesNeedToBeWashed);
        washingMachine.wash(function(err, done){
            if(err){
                reject(err);
                return; // to prevent run-till-completion
            }
            
            resolve(done);
        });
    })
 }


function doDishes(){
    return new Promise((resolve, reject)=>{
        let dishWasher = new Dishwasher();
        let dishesTBD = [];
        for (let dish of dishes){
            if(dish.isDirty()){
                dishesTBD.push(dish)
            }
        }
        
        dishWasher.startWashing(function(err, done){
            if(err){
                reject(err);
                return;
            }
            resolve(done);
            return;
        })
    })
}



let essay = new Essay();
let bci = new BrainComputerInterface({
    task: 'writeNewEssay',
    data: {
        topic: 'Promises and async/await'
    }
});
    
bci.pipe(essay.writerStream());

washClothes()
.then( data => console.log("Washing finished") )
.catch( err => console.error("Washing unfinished") )

doDishes()
.then( data => console.log("Dishes done") )
.catch( err => console.log("Dishes not done") )

This is what a "normal" Promise based code looks like. Some callbacks here and there, a lot of .thens and .catches. This is asynchronous. But we can do better, can't we?

Using async/await: the basics

Async/await is just some syntactic sugar over Promises. We can do absolutely anything without async/await. But then, we can "technically" write a web application's backend using nothing but C. Is that efficient?

What is an async function?
When you label a function with the async keyword, it returns a Promise. No ugly return new Pomise() with a callback. async function returns a Promise. When you return anything, the promise is resolved and when you throw a value, then the said Promise is rejected.

Apart from that, an async function also lets you use the await keyword.

What is await?
When you use await keyword, then that function asynchronously blocks the execution of the function until the operation to the right of await is finished.

// you can't use await outside an async function
function doSomething(){
    x = await XYZ(); // will throw an error
}

// you can use await only inside an async function
async function doSomething(){
    x = await XYZ(); // will NOT throw an error
}

// you can choose to await a synchronous operation
async function doSomething(){
    x = await XYZSync(); // this will work but here the await keyword doesn't actually do anything
}


// here we can see som

When you await a function that returns a Promise (async function or otherwise), your function will block till that function is finished. But it doesn't block everything. It blocks just the function. In other words, the function is paused, the entire thread doesn't stop. Below is an example

// this is an async function that downloads google.com and returns it
async function x(){
    let x = await fetch('https://google.com');
    return x
}

// this is a simple timeout with promises
function t(){
    return new Promise((resolve, reject) => {
        setTimeout(()=> {
            resolve("1 second complete")
        }, 1000)
    })
}

async function main(){

    t() // you can use thens in async
    .then( s => console.log(s) )
    .catch( e => console.error(e) )
    
    p = await x(); // this will pause this function, but won't stop t() called previously
    q = await t(); // this call will pause for 1 second.
    return 0;
}

Let's rewrite the above function using async/await

async function washClothes(){
    let washingMachine = new WashingMachine();
    let clothesNeedToBeWashed = [];
    for(let cloth of clothes){
        if cloth.isDirty(){
            clothesNeedToBeWashed.push(cloth)
        }
    }
    washingMachine.feedIn(clothesNeedToBeWashed);
    try{
    	let done = await washingMachine.wash();
        return done;
    } catch(err) {
        throw err;
    }
 }


async function doDishes(){ // declaring async makes it return Promise
    let dishWasher = new Dishwasher();
    let dishesTBD = [];
    for (let dish of dishes){
        if(dish.isDirty()){
            dishesTBD.push(dish);
        }
    }
    try {
       let done = await dishWasher.startWashing();
    } catch(err){
        throw err;
    }
}


async function main(){
    let essay = new Essay();
    let bci = new BrainComputerInterface({
        task: 'writeNewEssay',
        data: {
            topic: 'Promises and async/await'
        }
    });

    bci.pipe(essay.writerStream());

    let washClothesData = await washClothes();
    let doDishesData = await doDishes();
}

main();

Looks a lot cleaner. Doesn't it?

Sohan Basak

Hi, I am Sohan. A software engineer by profession, I am really passionate about algorithms, AI/ML, Maths and Physics. Play the guitar as a hobby, the maths behind music is fascinating.