The hard error handling case made easy with async/await

Ronald Chen
2 min readFeb 22, 2018

--

Let me show a surprisingly trivial looking async/await code which is actually hard to implement correctly with Promises.

async function(request, response, next) {
try {
await validate();
} catch (error) {
return response.status(400).send(error);
}

try {
const result = await doRealWork();
return response.send(result);
} catch (error) {
return next(error);
}
}

Looks simple enough, let’s try to convert this back to use Promises.

function(request, response, next) {
return validate()
.catch((error) => response.status(400).send(error))
.then(() => doRealWork())
.then((result) => response.send(result))
.catch(next);
}

There, what was so hard about that? Well, first of all the code is wrong. If validate() throws an Error, it goes up the stack instead of a 400. Second, on validation error doRealWork() is still called! The catch handles the error, and the Promise chain continues.

We can try to fix this by starting a Promise context and moving the catch down.

function(request, response, next) {
return Promise.resolve()
.then(() => validate())
.then(() => doRealWork())
.then((result) => response.send(result))
.catch((error) => response.status(400).send(error))
.catch(next);
}

Well obviously something is wrong here with a double catch. If doRealWork throws an error, it gets sent back as status 400 instead of going to next.

The real fix requires us to add a lot more code.

function(request, response, next) {
return Promise.resolve()
.then(() => validate())
.then(() => true)
.catch((error) => {
response.status(400).send(error);
return false;
})
.then((validationSuccess) => {
if (!validationSuccess) {
return;
}
return doRealWork()
.then((result) => response.send(result));
})
.catch(next);
}

We introduce a valid/invalid flag. And we also have to be careful to attach a then to doRealWork directly. If we move response.send(result) up one level, it would get called without a result if there was a validation error.

There is another way to write this code correctly using the second parameter of then, but folks, its not a looker.

// DO NOT NEST PROMISES LIKE THIS
function(request, response, next) {
return Promise.resolve()
.then(() => validate())
.then(() => doRealWork()
.then((result) => response.send(result), (error) => response.status(400).send(error))
.catch(next);
}

While that code is correct and short, it is extremely difficult to follow. This is a trap for the next person. They will try to simplify this code by unnesting the then and adding a catch.

function(request, response, next) {
return Promise.resolve()
.then(() => validate())
.then(() => doRealWork())
.then((result) => response.send(result))
.catch((error) => response.status(400).send(error))
.catch(next);
}

And guess what, its one of the wrong forms!

I hope this is an convincing argument on why to use async/await today!

--

--