Sunday, 21 March 2021

Async Calls and Fluent Interface

I guess almost everyone will agree in what an improvement the addition of the async/await syntax sugar to JavaScript and C# has meant for asynchronous programming. We know that the compiler will generate code using the then/ContinueWith methods (I've already linked this beautiful article years ago), but for the most part we can forget about the callback style that comes with these methods and enjoy the much cleaner "synchronous like" style allowed by async/await.

This said, there are still a few occasions when calling then ourselves will make our code cleaner than using async/await. I've come across one case recently and I'm going to share it here.

Let's say we have a fluent interface where some (or all) of the methods are asynchronous. For example, one class like this:


class DbHelper{
    addConnectionString(conSt){
        console.log("adding connection string");
        this.connectionString = conSt;
        return this;
    }

    encryptConnectionStringAsync(){
        console.log("encrypting connection string");
        this.connectionString = "XYXYXYXYX";
        return new Promise((resFn, rejFn) => setTimeout(() => resFn(this), 2000));
    }

    openConnectionAsync(){
        console.log("opening connection string");
        this.connection = {};
        return new Promise((resFn, rejFn) => setTimeout(() => resFn(this), 2000));
    }

    cacheBasicDataAsync(){
        console.log("caching basic data");
        this.dataCache = {};
        return new Promise((resFn, rejFn) => setTimeout(() => resFn(this), 2000));
    }

    runQuery(query){
        console.log("running query");
        return "query result";
    }

    runQueryAsync(){
        console.log("running async query");
        return new Promise((resFn, rejFn) => setTimeout(() => resFn("query result"), 2000));
    }
}

A chain of calls in this fluent class using await looks a bit confusing I would say:


let dbHelper = new DbHelper();

dbHelper = await (
	(await 
	    (await dbHelper.addConnectionString("conSt")
		.encryptConnectionStringAsync()
	    ).openConnectionAsync()
	).cacheBasicDataAsync()
);

let res = await dbHelper.runQueryAsync();
console.log("result: " + res);

However, using then to chain the calls looks much clearer in this case:


let dbHelper = new DbHelper();

dbHelper = await dbHelper.addConnectionString("conSt")
    .encryptConnectionStringAsync()
    .then(dbH => dbH.openConnectionAsync())
    .then(dbH => dbH.cacheBasicDataAsync());

let res = await dbHelper.runQueryAsync();
console.log("result: " + res);

No comments:

Post a Comment