Catch block wrapping a function that throws inside a setTimeout not executed

Tags: , ,



Experienced something strange recently, none of the catch blocks get executed:

function funcWillThrow() {
  try {
    setTimeout(() => {
      throw "Has thrown";
    }, 3000);
  } catch (error) {
    console.log(error)
  }
}

function caller() {
  funcWillThrow();
}

caller();

and

function funcWillThrow() {
    setTimeout(() => {
      throw "Has thrown";
    }, 3000);
}

function caller() {
  funcWillThrow();
}

try {
  caller();
} catch (error) {
  console.log(error);
}

And according to the mdn docs

Execution of the current function will stop (the statements after throw won’t be executed), and control will be passed to the first catch block in the call stack. If no catch block exists among caller functions, the program will terminate.

My guess is that something is happening with the callstack as stated in the docs. Any ideas as to what might be happening.

Answer

setTimeout calls the supplied function at a later time (3 seconds later in your specific code), and by then the function which called setTimeout has long since terminated.

To catch the exception in your later function, put the error handling in that function:

function funcWillThrow() {
  setTimeout(() => {
    try {
      throw "Has thrown";
    } catch (error) {
      console.log(error);
    }
  }, 3000);
}

Based on your comment below, you may be looking for Promises. Since the operation is asynchronous and happens outside of the stack which invoked it, in order to catch the error in that stack you’d need to await the operation. Ideally you’d make use of the reject functionality of the Promise. Something like this:

function funcWillThrow() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject("Has thrown");
    }, 3000);
  });
}

(async () => {
  try {
    await funcWillThrow();
  } catch (err) {
    console.log(err);
  }
})();

If you specifically need to be able to throw from within the setTimeout callback, you’d either need to catch there as well:

function funcWillThrow() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      try {
        throw "Has thrown";
      } catch (error) {
        reject(error);
      }
    }, 3000);
  });
}

(async () => {
  try {
    await funcWillThrow();
  } catch (err) {
    console.log(err);
  }
})();

Or customize setTimeout itself as a Promise and use normal error handling with that:

function myTimeout(ms) {
  return new Promise(function(resolve) {
    setTimeout(resolve, ms);
  });
}

(async () => {
  try {
    await myTimeout(3000).then(() => {
      throw "Has thrown";
    });
  } catch (err) {
    console.log(err);
  }
})();


Source: stackoverflow