How to set multiple properties without multiple try catch blocks and calls multiple services - Typescript?

Viewed 159

I am gathering data to set for a list of properties, however it calls many different services to help obtain this data. If there is a problem with any of the services it would throw an error. If an error is thrown I want the data to just return null for that specific property, and continue getting the rest of the data.

How do I do this without wrapping each service call in a try catch block?

I.E) this get all the properties, but if anything fails, the properties afterwards will not be set.

public async getAllProperties(){
try {
 const [studentYear, numOfCourses, numOfSemesters, startDate, gradDate] = 
await Promise.all([
   studentService.getStudentYear(), 
   courseService.getNumOfCourses(),
   semesterService.getNumOfSemesters(), 
   .... (etc)
])} catch (e){
   console.log("error with getting Properties");
 }
}

For example if I call getAllProperties() and the courseService throws an error, I want numOfCourses to be null, and for it to continue to check semesterService.getNumofCourse and continue setting the rest of the properties.

How can I do this without try catch for each service? I.E I don't want to write this for each property

try {
studentService.getStudentYear
} catch (e){
console.log("error in student Year");
studentYear = null;
}

try {
courseService.getNumOfCourses()
} catch (e){
console.log("error in get Num Of courses");
numOfCourses = null;
}

Thanks for any assistance or ideas!

2 Answers

I would suggest Promise.allSettled. This method returns a promise that resolves after all of the given promises have either fulfilled or rejected, with an array of objects that each describes the outcome of each promise. Source

Each promise must use try catch to detect an error, and return null if there is an error. You can create a function which will await a Promise, and either return the result or null if there is an error. This way you only need to use try catch once.

Here is a code example of it in use. https://stackblitz.com/edit/typescript-1nkhf4

function getAllProperties() {
  Promise.allSettled([getName(), getAge()]).then(data => {
    console.log(data)
  })
}

function getName() {
  return returnNullIfFail(() => new Promise((res, rej) => res("some name")))
}
function getAge() {
  return returnNullIfFail(() => new Promise((res, rej) => rej("some error")))
}

async function returnNullIfFail<T>(somePromise: () => Promise<T>) : Promise<T | null> {
  try {
    return await somePromise()
  } catch (err) {
    return null
  }
}

getAllProperties()

I like Alex's solution. But incase the resulting {status: "fullfilled", value: 2018} is a problem, here's my proposed solution:

var obj = {
  getStudentYear: () => new Promise(res => res(2018)),
  getNumOfCourses: () => new Promise(res => res(8)),
  getFailed: () => new Promise( (res, rej) => rej('failed'))
}

async function resolveOrNull(func){
  try{
    let x = await func()
    return x;
  } catch(err) {
    console.log(err);
    return null;
  }
}

async function getAllProperties() {
  return {
    studentYear: await resolveOrNull(obj.getStudentYear),
    numOfCourses: await resolveOrNull(obj.getNumOfCourses),
    getFailed: await resolveOrNull(obj.getFailed)
  }
}

getAllProperties()
  .then(res => console.log(res))
  .catch(err => console.log(err))

Edit: So let's say you wanted to make a test to your code. You mock the functionality and now you want to make sure the resulting studentYear was equal to an expected value of 2018:

If you're using a primitive value you can just do this:

 let studentYear = test.getStudentYear() // returns 2018
 studentYear === 2018 // this is TRUE

If you're using an object:

let studentYear = test.getStudentYear() // returns {status: "fullfilled", value: 2018}
studentYear === {status: "fullfilled", year: 2018} // this is FALSE

When Javascript compares one object to another object, it's compared by it's reference, not by it's values. I've run into plenty of other headache's when using objest/arrays and try my best to avoid it when possible. Which it often isn't, and honestly it's usually fine, but they do tend to over complicate things that could otherwise be very simple.

Edit2: (This is starting to get a bit "sloppy")

//... previous code
let globalVariable;

getAllProperties()
  .then((res) => {
    globalVariable = res;
    useGlobal();
  })
  .catch((err) => console.log(err));

function useGlobal() {
  console.log(globalVariable); // This contains the correct obj
  //  anything else that uses the global variable 
  //    should be called inside this function
}

console.log(globalVariable); // This is undefined

Edit3: (This is more along the lines of what you should be doing, but there's quite a few different ways to handle the problems that come with async code)

getAllProperties()
  .then(useAllProperties)
  .catch((err) => console.log(err));

function useAllProperties = (res) => {
  console.log(res);
  // run code that needs the data
}
Related