This article's content
Error handling with async /await in TypeScript

The problem

We don't know whether function A or B threw the error.

function getTrueOrFalse() {
  return !Math.floor(Math.random() * Math.floor(2));
}

function A() : Promise<string> {
  return getTrueOrFalse() ?
    Promise.resolve("A is good") :
    Promise.reject("Backend failure");
}

function B(val) : Promise<string> {
  return getTrueOrFalse() ?
    Promise.resolve("B is good") :
    Promise.reject("Backend failure");
}

async function runAll() : Promise<any> {
  try {
    const resultA = await A();
    console.log("A: " + resultA);

    const resultB = await B(resultA);
    console.log("B: " + resultB);

  } catch(exc) {
    console.log(exc + ". Exception! But from where?");
  }  
}

runAll();

To fix this we could nest even more try and catch blocks, but that is ugly code.

Solution using multiple try..catch

That's my favorite one. Looks clean! Take note that in this example resultA must be visible in the second try-block, so we have to define resultA above the first try-block.

function getTrueOrFalse() {
  return !Math.floor(Math.random() * Math.floor(2));
}

function A() : Promise<string> {
  return getTrueOrFalse() ?
   Promise.resolve("A is good") :
   Promise.reject("Backend failure");
}

function B(val) : Promise<string> {
  return getTrueOrFalse() ?
   Promise.resolve("B is good") :
   Promise.reject("Backend failure");
}

async function runAll() : Promise<any> {
  let resultA;

  try {
    resultA = await A();
    console.log("A: " + resultA);
  } catch(exc) {
    return console.log(exc + ". Exception from A");
  }
 
  try {
    const resultB = await B(resultA);
    console.log("B: " + resultB);

  } catch(exc) {
    return console.log(exc + ". Exception from B");
  }
}

runAll();

A solution without try..catch

In this solution we totally avoid using try..catch() by writing a small handle function that always returns a promise which contains the data in success case or the error in case of failure.

function getTrueOrFalse() {
  return !Math.floor(Math.random() * Math.floor(2));
}

function A() : Promise<string> {
  return getTrueOrFalse() ?
   Promise.resolve("A is good") :
   Promise.reject("Backend failure");
}

function B(val) : Promise<string> {
  return getTrueOrFalse() ?
   Promise.resolve("B is good") :
   Promise.reject("Backend failure");
}

function handle(prom) {
  return prom.then((res) => [res]).catch((err) => [,err]);
}

async function runAll() : Promise<any> {
  const [resultA, errA] = await handle(A());
  if(errA) {
    return console.log(errA + " from A");
  }
  console.log("A: " + resultA);

  const [resultB, errB] = await handle(B(resultA));
  if(errB) {
    return console.log(errB + " from B");
  }
  console.log("B: " + resultB);
}

runAll();

But this is also somewhat ugly, because you have to wrap all your async functions into a handler.

About Author

Mathias Bothe To my job profile

I am Mathias, born 40 years ago in Heidelberg, Germany. Today I am living in Munich and Stockholm. I am a passionate IT freelancer with more than 16 years experience in programming, especially in developing web based applications for companies that range from small startups to the big players out there. I am founder of bosy.com, creator of the security service platform BosyProtect© and initiator of several other software projects.