6 Reasons Why JavaScript’s Async/Await Blows Promises Away (Tutorial)
Mostafa Gaafar
4K59

Promise and try objects with async/await

The async / await construct blows promises away, but promises offer properties and methods that still exist underneath. Let us look at how we can wrap even more nicely one useful method of promises into the async / await construct. We focus on the catch method.

The tl;dr; version is can we implement efficiently a corresponding construct, say try / endtry for the synchronous case, thus avoiding adding extra try / catch blocks and simply declare a function to be a try function and then catch the outcome, just as we can do in async / await in the asynchronous case?

Note added: After I wrote this post and started to try implement the try / endtry construct, I realized that it can be syntactic sugar over the old good try / catch construct. I will post the code and add a link here. I also implemented the entire async/await construct using try objects.

Warning: If you are very much used to the try somecode catch e handler(e) format and love to see try blocks, you might not understand the significance of this question. The goal is to be able to declare a function f to be a try block using try f(a,b) {body}and the body becomes the try block. Then we use f(a,b).catch(handler), no extra try block.

The connection with async/ await is simply that the try keyword is just like async, but for the synchronous case. To use the value f(a,b), we need a keyword endtry. The keyword endtry is just like await .

Mostafa Gaafar argues and he is not alone that we can do every thing with the old try / catch construct. Syntactically, it’s true, but, of course, in the asynchronous case, the exception occurs in the execution of the promise and it simply bubbles up in a try / catch block.

What seems to be suggested is that we should not use the catch method anymore. However, though it is possible to use the try / catch construct only, I will argue that doing this all the times is not necessarily nice. First, I present some code that does the opposite.

// This code uses only the catch method. 
// It would not be efficient in the synchronous case,
// but this is only an implementation issue.
function UE(name,message) {
this.name = name;
this.message = message;
}
var n = process.argv[2]; // Selecting the error thrown.
console.log('n: ' + n);
// Function f and its handler
async function f() {
if (n == 1) throw new UE('TestError1','to be handled as soon as possible');
else if (n == 2) throw new UE('TestError2', 'hard to handle');
return 'OK in f'; // n = 0
}
function errorHandlerF (e) {
if (e.name == 'TestError1') return `"${e.name}:${e.message}" handled in f handler`;
throw e; // We only handle TestError1
}
// Function g() totally screwed-up
async function g() { undeefined }
// Function h and its handler
async function h() {
if (n <= 2) return await f().catch(errorHandlerF);
else if (n == 3) return await g();
else throw new UE('TestError3', 'thrown in h'); // n = 4
}
function errorHandlerH (e) {
console.log(`"${e.name}:${e.message}" handled in h handler`)
}
// The call to h could be put in an async function to avoid then.
h().then((v) => console.log(v))
.catch(errorHandlerH);

See how non intrusive is error handling. We have simple calls to the catch method that go well along with the flow of execution. One might say that this code does not have try blocks, but this is not really true. Every async function defines a try block — it can be catched. It’s just not called a try block. To make it more explicit, we could have renamed async to asynctry. Promises are a lot about error handling — it’s where there power lie, when compared to the ordinary callback approach.

Let us see how the same code can be written with try / catch instead:

// This code uses only catch in try/catch - no catch method.
function UE(name,message) {
this.name = name;
this.message = message;
}
var n = process.argv[2]; // Selecting the error thrown.
console.log('n: ' + n);
// Function f and its handler
async function f() {
try {
if (n == 1) throw new UE('TestError1','to be handled as soon as possible');
else if (n == 2) throw new UE('TestError2', 'hard to handle');
return 'OK in f'; // n = 0
}
catch(e) { errorHandlerF (e)}
}
function errorHandlerF (e) {
if (e.name == 'TestError1') return `"${e.name}:${e.message}" handled in f handler`;
throw e; // We only handle TestError1
}
// Function g() totally screwed-up
// We don't catch g(), so no need for a try/ catch block.
async function g() {undeefined}
// Function h and its handler
async function h() {
try {
if (n <= 2) return await f();
else if (n == 3) return await g();
else throw new UE('TestError3', 'thrown in h'); // n = 4
}
catch (e) {errorHandlerH(e)}
}
function errorHandlerH (e) {
console.log(`"${e.name}:${e.message}" handled in h handler`)
}
// The call to h could be put in an async function to avoid then.
h().then((v) => console.log(v))

We added, in my opinion redundant, try / catch blocks, only because we did not want to catch the already existing async blocks. This should convince a few that it would be normal to catch the existing async blocks instead of adding extra extra try / catch blocks.

Others feel instead that these extra try / catch blocks make it more explicit that we manage error and it’s good. Perhaps, the async keyword does not convey enough the concept of try block and the reaction would have been different had we used asynctry instead. In any case, if people love so much extra try / catch blocks, they should use them as they want.

The issue is that in the current implementation, if we care about efficiency, we SHOULD use the old good try / catch construct inside synchronous functions . It’s weird because the current trend is to make the asynchronous case looks like the synchronous case — this is where the power of async/ awaitlies—and a special restriction limiting catch to the asynchronous case does the opposite. In any case, a catch method about error handling should not be limited to the asynchronous case, especially not if the goal is only to avoid a little work to implement a catch method that is efficient.

Is it that is hard to implement a catch method that is efficient in the synchronous case? We need a prototype for try objects with a catch method. The counterparts of async/ await could be try / endtry. It seems easy to implement, because it is less than what is needed for promise objects, but can it be as efficient than the ordinary try/catch`construct?

I end this post with this question. An example of how it will be used is already given in the code snippet that I posted, except that async / await should be replaced by try / endtry.