The hard error handling case made easy with async/await

Ronald Chen
Feb 22, 2018 · 2 min read

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!

Ronald Chen
Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade