Throw new error in promise

I have read several articles on this subject, but it is still not clear to me if there is a difference between Promise.reject vs. throwing an error. For example, Using Promise.reject return

There’s one difference — which shouldn’t matter — that the other answers haven’t touched on, so:

If the fulfillment handler passed to then throws, the promise returned by that call to then is rejected with what was thrown.

If it returns a rejected promise, the promise returned by the call to then is resolved to that promise (and will ultimately be rejected, since the promise it’s resolved to is rejected), which may introduce one extra async «tick» (one more loop in the microtask queue, to put it in browser terms).

Any code that relies on that difference is fundamentally broken, though. :-) It shouldn’t be that sensitive to the timing of the promise settlement.

Here’s an example:

If you run that, as of this writing you get:

Error from usingThrow: 2 is not 42!
Error from usingReject: 1 is not 42!

Note the order.

Compare that to the same chains but both using usingThrow:

which shows that the rejection handlers ran in the other order:

Error from usingThrow: 1 is not 42!
Error from usingThrow: 2 is not 42!

I said «may» above because there’s been some work in other areas that removed this unnecessary extra tick in other similar situations if all of the promises involved are native promises (not just thenables). (Specifically: In an async function, return await x originally introduced an extra async tick vs. return x while being otherwise identical; ES2020 changed it so that if x is a native promise, the extra tick is removed where there is no other difference.)

Again, any code that’s that sensitive to the timing of the settlement of a promise is already broken. So really it doesn’t/shouldn’t matter.

In practical terms, as other answers have mentioned:

  • As Kevin B pointed out, throw won’t work if you’re in a callback to some other function you’ve used within your fulfillment handler — this is the biggie
  • As lukyer pointed out, throw abruptly terminates the function, which can be useful (but you’re using return in your example, which does the same thing)
  • As Vencator pointed out, you can’t use throw in a conditional expression (? :), at least not for now

Other than that, it’s mostly a matter of style/preference, so as with most of those, agree with your team what you’ll do (or that you don’t care either way), and be consistent.

The catch() method of a Promise object schedules a function to be called when the promise is rejected. It immediately returns an equivalent Promise object, allowing you to chain calls to other promise methods. It is a shortcut for Promise.prototype.then(undefined, onRejected).

Try it

Syntax

catch(onRejected)

catch((reason) => {
  // rejection handler
})

Parameters

onRejected

A Function called when the Promise is rejected. This function has one parameter: the rejection reason.

Return value

Returns a new Promise. This new promise is always pending when returned, regardless of the current promise’s status. It’s eventually rejected if onRejected throws an error or returns a Promise which is itself rejected; otherwise, it’s eventually fulfilled.

Description

The catch method is used for error handling in promise composition. Since it returns a Promise, it can be chained in the same way as its sister method, then().

If a promise becomes rejected, and there are no rejection handlers to call (a handler can be attached through any of then(), catch(), or finally()), then the rejection event is surfaced by the host. In the browser, this results in an unhandledrejection event. If a handler is attached to a rejected promise whose rejection has already caused an unhandled rejection event, then another rejectionhandled event is fired.

catch() internally calls then() on the object upon which it was called, passing undefined and onRejected as arguments. The value of that call is directly returned. This is observable if you wrap the methods.

// overriding original Promise.prototype.then/catch just to add some logs
((Promise) => {
  const originalThen = Promise.prototype.then;
  const originalCatch = Promise.prototype.catch;

  Promise.prototype.then = function (...args) {
    console.log("Called .then on %o with arguments: %o", this, args);
    return originalThen.apply(this, args);
  };
  Promise.prototype.catch = function (...args) {
    console.error("Called .catch on %o with arguments: %o", this, args);
    return originalCatch.apply(this, args);
  };
})(Promise);

// calling catch on an already resolved promise
Promise.resolve().catch(function XXX() {});

// Logs:
// Called .catch on Promise{} with arguments: Arguments{1} [0: function XXX()]
// Called .then on Promise{} with arguments: Arguments{2} [0: undefined, 1: function XXX()]

This means that passing undefined still causes the returned promise to be rejected, and you have to pass a function to prevent the final promise from being rejected.

Because catch() just calls then(), it supports subclassing.

Note: The examples below are throwing instances of Error. As with synchronous throw statements, this is considered a good practice; otherwise, the part doing the catching would have to perform checks to see if the argument was a string or an error, and you might lose valuable information such as stack traces.

Examples

Using and chaining the catch() method

const p1 = new Promise((resolve, reject) => {
  resolve("Success");
});

p1.then((value) => {
  console.log(value); // "Success!"
  throw new Error("oh, no!");
})
  .catch((e) => {
    console.error(e.message); // "oh, no!"
  })
  .then(
    () => console.log("after a catch the chain is restored"),
    () => console.log("Not fired due to the catch"),
  );

// The following behaves the same as above
p1.then((value) => {
  console.log(value); // "Success!"
  return Promise.reject("oh, no!");
})
  .catch((e) => {
    console.error(e); // "oh, no!"
  })
  .then(
    () => console.log("after a catch the chain is restored"),
    () => console.log("Not fired due to the catch"),
  );

Gotchas when throwing errors

Throwing an error will call the catch() method most of the time:

const p1 = new Promise((resolve, reject) => {
  throw new Error("Uh-oh!");
});

p1.catch((e) => {
  console.error(e); // "Uh-oh!"
});

Errors thrown inside asynchronous functions will act like uncaught errors:

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    throw new Error("Uncaught Exception!");
  }, 1000);
});

p2.catch((e) => {
  console.error(e); // This is never called
});

Errors thrown after resolve is called will be silenced:

const p3 = new Promise((resolve, reject) => {
  resolve();
  throw new Error("Silenced Exception!");
});

p3.catch((e) => {
  console.error(e); // This is never called
});

catch() is not called if the promise is fulfilled

// Create a promise which would not call onReject
const p1 = Promise.resolve("calling next");

const p2 = p1.catch((reason) => {
  // This is never called
  console.error("catch p1!");
  console.error(reason);
});

p2.then(
  (value) => {
    console.log("next promise's onFulfilled");
    console.log(value); // calling next
  },
  (reason) => {
    console.log("next promise's onRejected");
    console.log(reason);
  },
);

Specifications

Specification
ECMAScript Language Specification
# sec-promise.prototype.catch

Browser compatibility

BCD tables only load in the browser

See also

Время прочтения
5 мин

Просмотры 17K

Доброго времени суток, друзья!

Хотел бы я знать об этих ошибках, когда изучал JavaScript и промисы.

Всякий раз, когда ко мне обращается какой-нибудь разработчик и жалуется на то, что его код не работает или медленно выполняется, я прежде всего обращаю внимание на эти ошибки. Когда я начал программировать 4 года назад, я не знал о них и привык их игнорировать. Однако после назначения в проект, который обрабатывает около миллиона запросов в течение нескольких минут, у меня не было другого выбора, кроме как оптимизировать свой код (поскольку мы достигли уровня, когда дальнейшее вертикальное масштабирование стало невозможным).

Поэтому в данной статье я бы хотел поговорить о самых распространенных ошибках при работе с промисами в JS, на которые многие не обращают внимания.

Ошибка № 1. Использование блока try/catch внутри промиса

Использовать блок try/catch внутри промиса нецелесообразно, поскольку если Ваш код выдаст ошибку (внутри промиса), она будет перехвачена обработчиком ошибок самого промиса.

Речь идет вот о чем:

new Promise((resolve, reject) => {
  try {
    const data = someFunction()
    // ваш код
    resolve()
  } catch(e) {
    reject(e)
  }
})
  .then(data => console.log(data))
  .catch(error => console.log(error))

Вместо этого позвольте коду обработать ошибку вне промиса:

new Promise((resolve, reject) => {
  const data = someFunction()
  // ваш код
  resolve(data)
})
  .then(data => console.log(data))
  .catch(error => console.log(error))

Это будет работать всегда, за исключением случая, описанного ниже.

Ошибка № 2. Использование асинхронной функции внутри промиса

При использовании асинхронной функции внутри промиса возникают некоторые неприятные побочные эффекты.

Допустим, Вы решили выполнить некоторую асинхронную задачу, добавили в промис ключевое слово «async», и Ваш код выдает ошибку. Однако теперь Вы не можете обработать эту ошибку ни с помощью .catch(), ни с помощью await:

// этот код не сможет перехватить ошибку
new Promise(async() => {
  throw new Error('message')
}).catch(e => console.log(e.message))

// этот код также не сможет перехватить ошибку
(async() => {
  try {
    await new Promise(async() => {
      throw new Error('message')
    })
  } catch(e) {
    console.log(e.message)
  }
})();

Каждый раз, когда я встречаю асинхронную функцию внутри промиса, я пытаюсь их разделить. И у меня это получается в 9 из 10 случаев. Тем не менее, это не всегда возможно. В таком случае у Вас нет другого выбора, кроме как использовать блок try/catch внутри промиса (да, это противоречит первой ошибке, но это единственный выход):

new Promise(async(resolve, reject) => {
  try {
    throw new Error('message')
  } catch(error) {
    reject(error)
  }
}).catch(e => console.log(e.message))

// или используя async/await
(async() => {
  try {
    await new Promise(async(resolve, reject) => {
      try {
        throw new Error('message')
      } catch(error) {
        reject(error)
      }
    })
  } catch(e) {
    console.log(e.message)
  }
})();

Ошибка № 3. Забывать про .catch()

Эта одна из тех ошибок, о существовании которой даже не подозреваешь, пока не начнется тестирование. Либо, если Вы какой-нибудь атеист, который не верит в тесты, Ваш код обязательно рухнет в продакшне. Потому что продакшн строго следует закону Мерфи, который гласит: «Anything that can go wrong will go wrong» (можно перевести так: «Если что-то может пойти не так, это обязательно произойдет»; аналогией в русском языке является «закон подлости» — прим. пер.).

Для того, чтобы сделать код элегантнее, можно обернуть промис в try/catch вместо использования .then().catch().

Ошибка № 4. Не использовать Promise.all()

Promise.all() — твой друг.

Если Вы профессиональный разработчик, Вы наверняка понимаете, что я хочу сказать. Если у Вас есть несколько не зависящих друг от друга промисов, Вы можете выполнить их одновременно. По умолчанию, промисы выполняются параллельно, однако если Вам необходимо выполнить их последовательно (с помощью await), это займет много времени. Promise.all() позволяет сильно сократить время ожидания:

const {promisify} = require('util')
const sleep = promisify(setTimeout)

async function f1() {
  await sleep(1000)
}
async function f2() {
  await sleep(2000)
}
async function f3() {
  await sleep(3000)
}

// выполняем последовательно
(async() => {
  console.time('sequential')
  await f1()
  await f2()
  await f3()
  console.timeEnd('sequential') // около 6 секунд
})();

Теперь с Promise.all():

(async() => {
  console.time('concurrent')
  await Promise.all([f1(), f2(), f3()])
  console.timeEnd('concurrent') // около 3 секунд
})();

Ошибка № 5. Неправильное использование Promise.race()

Promise.race() не всегда делает Ваш код быстрее.

Это может показаться странным, но это действительно так. Я не утверждаю, что Promise.race() — бесполезный метод, но Вы должны четко понимать, зачем его используете.

Вы, например, можете использовать Promise.race() для запуска кода после разрешения любого из промисов. Но это не означает, что выполнение кода, следующего за промисами, начнется сразу же после разрешения одного из них. Promise.race() будет ждать разрешения всех промисов и только после этого освободит поток:

const {promisify} = require('util')
const sleep = promisify(setTimeout)

async function f1() {
  await sleep(1000)
}
async function f2() {
  await sleep(2000)
}
async function f3() {
  await sleep(3000)
}

(async() => {
  console.time('race')
  await Promise.race([f1(), f2(), f3()])
})();

process.on('exit', () => {
 console.timeEnd('race') // около 3 секунд, код не стал быстрее!
})

Ошибка № 6. Злоупотребление промисами

Промисы делают код медленнее, так что не злоупотребляйте ими.

Часто приходится видеть разработчиков, использующих длинную цепочку .then(), чтобы их код выглядел лучше. Вы и глазом не успеете моргнуть, как эта цепочка станет слишком длинной. Для того, чтобы наглядно убедиться в негативных последствиях такой ситуации, необходимо (далее я немного отступлю от оригинального текста для того, чтобы описать процесс подробнее, нежели в статье — прим. пер.):

1) создать файл script.js следующего содержания (с лишними промисами):

new Promise((resolve) => {
  // некий код, возвращающий данные пользователя
  const user = {
    name: 'John Doe',
    age: 50,
  }
  resolve(user)
}).then(userObj => {
    const {age} = userObj
    return age
}).then(age => {
  if(age > 25) {
    return true
  }
throw new Error('Age is less than 25')
}).then(() => {
  console.log('Age is greater than 25')
}).catch(e => {
  console.log(e.message)
})

2) открыть командную строку (для пользователей Windows: чтобы открыть командную строку в папке с нужным файлом, зажимаем Shift, кликаем правой кнопкой мыши, выбираем «Открыть окно команд»), запустить script.js с помощью следующей команды (должен быть установлен Node.js):

node --trace-events-enabled script.js

3) Node.js создает файл журнала (в моем случае node_trace.1.txt) в папке со скриптом;

4) открываем Chrome (потому что это работает только в нем), вводим в адресной строке «chrome://tracing»;

5) нажимаем Load, загружаем файл журнала, созданного Node.js;

6) открываем вкладку Promise.

Видим примерно следующее:

Зеленые блоки — промисы, выполнение каждого из которых занимает несколько миллисекунд. Следовательно, чем больше будет промисов, тем дольше они будут выполняться.

Перепишем script.js:

new Promise((resolve, reject) => {
  const user = {
    name: 'John Doe',
    age: 50,
  }
  if(user.age > 25) {
    resolve()
  } else {
    reject('Age is less than 25')
  }
}).then(() => {
  console.log('Age is greater than 25')
}).catch(e => {
  console.log(e.message)
})

Повторим «трассировку».

Видим следующее:

Зеленых блоков (промисов) стало меньше, а значит время выполнения кода сократилось.

Таким образом, использовать несколько промисов следует только в том случае, если Вам необходимо выполнить некоторый асинхронный код.

Благодарю за внимание.

  1. Documentation
  2. Error handling with async/await and promises, n² ways to shoot yourself in the foot

Table of Contents

  • Overview of async exception handling
  • Handling errors in promises locally

    • Thrown errors
    • Rejected promises
    • Errors thrown in a different call stack
  • Handling errors in promises globally
  • Logging errors in promises with CatchJS

Browsers now have native support for doing asynchronous calls via async/await. This is nice. It is essentially syntax support for promises.

Unfortunately, the error handling story for all this not so nice. The mechanisms are interwoven, and don’t always interact with each other in a clean way.
The following table summarizes this, and we’ll dig into it further in the text below.

Overview of async exception handling

If I cause an error with 🢂
can I catch it with 🢃?
throw new Error() reject()
try {} catch {} Yes, but if the throw happens in a Promise it must have been awaited with the await syntax, and resolve must not have been called before the throw. Will not catch errors thrown in another call stack via a setTimeout() or setInterval() callback. Yes, but only if the function was called with the await syntax, and only if resolve() has not been called for the promise already.
promise.catch(e => {}) Yes, unless resolve() was called earlier or the error happened in an asynchronous callback function, for example, a function passed to setTimeout(). Yes, unless resolve() was called earlier.
window.onunhandledrejection Yes, but not until script execution has completed, your call stack is unwound, and control is yielded back to the runtime, and none of the other mechanisms have dealt with error up until then.
window.onerror Not if the error was thrown in a Promise. No.

Real footage of the async error delegation mechanism

How did we end up with this? Well, when adding new features to a system, if every feature number n has to interact
with all of the existing n-1 features, you get an O(n²) growth in feature-feature interactions. So for a linear
growth in features, you get a quadratic growth in complexity. This actually explains why most big software projects
fail, and why disentangling features is so important. It’s also what has happened to async error handling. We
started with simple callback functions, and found that it was a mess. Then we fixed that mess with Promises,
and found that that solution also was a bit of a mess. Then we fixed that mess with async/await.

So let’s dig into the current mess.

Handling errors in promises locally

Promises, they break before they’re made

Sometimes, sometimes

— The Strokes, in a post to the WHATWG mailing list

Thrown errors

When an error is thrown in an async function, you can catch it with a try {} catch {}. So this works as you’d expect:

async function fails() {
    throw Error();
}

async function myFunc() {
    try {
        await fails();
    } catch (e) {
        console.log("that failed", e); 
    }
}

This is syntax sugar for what you might have been doing with promises earlier:

fails().catch(e => {
    console.log("That also failed", e); 
});

In fact, anywhere you use the keyword await, you can remove await and
do the traditional .then() and .catch() calls. This is because the async
keyword implicitly creates a Promise for its function.

The only difference between these two is that the callback for catch() has
it’s own execution context, i.e. variable scope works like you’d expect it to.

Rejected promises

So with Promises, it turns out you have another way of throwing errors, other than using throw, namely by calling reject():

function fails2() {
    return new Promise((resolve, reject) => {
        reject(new Error());
    });
}

async function myFunc2() {
    try {
        await fails2();
    } catch (e) {
        console.log("that failed", e); 
    }
}

Errors passed to reject() can be caught with both try {} catch {} and with the .catch() method.
So you have two ways to throw errors, and two ways to catch errors. This is more complex than we’d like, but at least
each way of catching errors will catch both ways of throwing them, so the complexity here isn’t fully as bad as it could have been.

Errors thrown in a different call stack

There’s more troubly to be had though. If you’re creating Promise yourself,
chances are you’re using either a setTimeout() or a setInterval(),
or in some way calling a callback function when some operation is done. These callbacks will be
called from a different call stack, which means that thrown errors will propagate to somewhere
that is not your code.

Consider this example:

function fails3() {
    return new Promise((resolve, reject) => {
        setTimeout(function() {
            throw new Error();
        }, 100);
    });
}

async function myFunc3() {
    try {
        await fails3();
    } catch (e) {
        console.log("that failed", e); //<-- never gets called
    }
}

The error produced here is never caught by the try {} catch {}, because it is thrown on a
different call stack. Using the .catch(() => {}) method would have the same problem.

The way to have an error propagate across such callbacks is to use the reject() function, like so:

function fails4() {
    return new Promise((resolve, reject) => {
        setTimeout(function() {
            reject(new Error());
        }, 100);
    });
}

async function myFunc4() {
    try {
        await fails4();
    } catch (e) {
        console.log("that failed", e); //<-- this gets called
    }
}

This is presumably the main reason why the reject/resolve paradigm was introduced in the first place.

Sidenote: Why reject/resolve kind of sucks.

Calling reject(new Error()) in a promise is much like doing throw Error(),
except for a major difference: It’s just a function call, so it doesn’t break the execution flow
like throw does. This means you can write paradoxical code that both rejects
and resolves, like this:

function schrödinger() {
    return new Promise((resolve, reject) => {
        reject(new Error());
        resolve("great success");
    });
}

Here both reject() and resolve() will be called. So which will win?
The answer is whichever function was called first.

Now look at this weirdo:

function schrödinger2() {
    return new Promise((resolve, reject) => {
        throw resolve("huh"); //<-- this throw is executed
    });
}
async function callAsync() {
    try {
        await schrödinger2();
    } catch (e) {
        console.log("caught error", e); //<-- yet, this is never reached
    }
}

Here the promise has a single line of code, a throw statement. Yet, the try {} catch {}
is never triggered. This is because resolve was called, and the rule still is that whatever was called
first is what wins. So the throw is executed, but it is silently swallowed by the runtime. This
is bound to cause endless confusion.

These problems happen because resolve() and reject() are near duplicates
of return and throw.
I’ll claim that the only reason we have reject/resolve is to be able to move errors across call stack boundaries.
But it’s a mediocre fix for that, for several reasons. It only moves the errors you expect, so e.g.
an unexpected NullReferenceException will not be moved across boundaries unless you explicitly call
reject() with it yourself. Also, the fact that it duplicates core language features
causes a lot of problems, as seen above.

There’s a cleaner design for this. C# has had async/await since before
people started talking about it in JavaScript. There, exceptions thrown in the async callbacks are
caught, and then rethrown such that they propagate to the site that is awaiting the async operation.
JavaScript could implement this by providing substitutes for setTimeout and setInterval with new semantics for errors,
and we could ditch this resolve/reject stuff in favor of return/throw.
This would also cut down the Promises spec by 90%.

Handling errors in promises globally

So we know how to catch errors with try {} catch {} and similar mechanisms. What about
when you want to set up a global catch-all handler for all unhandled errors, for example to log these
errors to a server?

Well, how do you even tell if an error in a promise is unhandled?
When dealing with promises, you have no way of knowing if an error will be handled some time in the future.
The promise might call reject(), and some code might come along 10 minutes later and
call .catch(() => {}) on that promise, in which case the error will be handled.
For this reason, the global error handler in Promise libraries like Q and Bluebird has been
named onPossiblyUnhandledRejection, which is a fitting name. In native Promises,
this function is called onunhandledrejection, but they still can only tell if a
rejection has been unhandled so far. So onunhandledrejection is only triggered
when the currently running script has completed and control has been yielded back to the runtime,
if nothing else has caught the error in the meantime.

You can set up your global handler for async exceptions and rejections like this:

window.onunhandledrejection = function(evt) { /*Your code*/ }

or:

window.addEventListener("unhandledrejection", function(evt) { /*Your code*/ })

Here evt is an object of type PromiseRejectionEvent.
evt.promise is the promise that was rejected, and evt.reason holds whatever object was passed to the reject() function.

This is all nice and dandy, except for this:

No one except Chrome implement it (well, Chrome, and Chromium based browsers).
It is coming to Firefox, and presumably to Safari and Edge as well. But not yet.
To make matters worse, there is no good work around for these browsers, other than
not using native Promises, and relying on a library like Q or Bluebird instead.
Hopefully native support will arrive for these browsers soon.

Summer 2019 update: unhandledrejection is now supported by Chrome, FireFox, Edge and Safari.

Logging errors in promises with CatchJS

CatchJS instruments the browser with a global error handler, in order to track uncaught errors that occur. Deployment is simply done by dropping in a script file.

<script src="https://cdn.catchjs.com/catch.js"></script>

With this, uncaught errors get logged, along with various telemetry, which can include screenshots and click trails.

CatchJS does not attach it self to the onunhandledrejection handler. If you want this, you can set up such forwarding manually.

window.onunhandledrejection = function(evt) {
    console.error(evt.reason);
}

CatchJS will instrument console.error, so these errors will be logged to your remote persistent log, as well as to the developers console.


Summary: in this tutorial, you will learn how to deal with error handling in promises.

Suppose that you have a function called getUserById() that returns a Promise:

function getUserById(id) { return new Promise((resolve, reject) => { resolve({ id: id, username: 'admin' }); }); }

Code language: JavaScript (javascript)

Normal error

First, change the getUserById() function to throw an error outside the promise:

function getUserById(id) { if (typeof id !== 'number' || id <= 0) { throw new Error('Invalid id argument'); } return new Promise((resolve, reject) => { resolve({ id: id, username: 'admin' }); }); }

Code language: JavaScript (javascript)

Second, handle the promise by using both then() and catch() methods:

getUserById('a') .then(user => console.log(user.username)) .catch(err => console.log(err));

Code language: JavaScript (javascript)

The code throws an error:

Uncaught Error: Invalid id argument

Code language: JavaScript (javascript)

When you raise an exception outside the promise, you must catch it with try/catch:

try { getUserById('a') .then(user => console.log(user.username)) .catch(err => console.log(`Caught by .catch ${error}`)); } catch (error) { console.log(`Caught by try/catch ${error}`); }

Code language: JavaScript (javascript)

Output:

Caught by try/catch Error: Invalid id argument

Code language: JavaScript (javascript)

Errors inside the Promises

We change the getUserById() function to throw an error inside the promise:

let authorized = false; function getUserById(id) { return new Promise((resolve, reject) => { if (!authorized) { throw new Error('Unauthorized access to the user data'); } resolve({ id: id, username: 'admin' }); }); }

Code language: JavaScript (javascript)

And consume the promise:

try { getUserById(10) .then(user => console.log(user.username)) .catch(err => console.log(`Caught by .catch ${error}`)); } catch (error) { console.log(`Caught by try/catch ${error}`); }

Code language: JavaScript (javascript)

Output:

Caught by .catch Error: Unauthorized access to the user data

Code language: JavaScript (javascript)

If you throw an error inside the promise, the catch() method will catch it, not the try/catch.

If you chain promises, the catch() method will catch errors that occurred in any promise. For example:

promise1 .then(promise2) .then(promise3) .then(promise4) .catch(err => console.log(err));

Code language: JavaScript (javascript)

In this example, if any error in the promise1, promise2, or promise4, the catch() method will handle it.

Calling reject() function

Throwing an error has the same effect as calling the reject() as illustrated in the following example:

let authorized = false; function getUserById(id) { return new Promise((resolve, reject) => { if (!authorized) { reject('Unauthorized access to the user data'); } resolve({ id: id, username: 'admin' }); }); } try { getUserById(10) .then(user => console.log(user.username)) .catch(err => console.log(`Caught by .catch ${err}`)); } catch (error) { console.log(`Caught by try/catch ${error}`); }

Code language: JavaScript (javascript)

In this example, instead of throwing an error inside the promise, we called the reject() explicitly. The catch() method also handles the error in this case.

Missing the catch() method

The following example does not provide the catch() method to handle the error inside the promise. It will cause a runtime error and terminate the program:

function getUserById(id) { return new Promise((resolve, reject) => { if (!authorized) { reject('Unauthorized access to the user data'); } resolve({ id: id, username: 'admin' }); }); } try { getUserById(10) .then(user => console.log(user.username)); // the following code will not execute console.log('next'); } catch (error) { console.log(`Caught by try/catch ${error}`); }

Code language: JavaScript (javascript)

Output:

Uncaught (in promise) Unauthorized access to the user data

If the promise is resolved, you can omit the catch() method. In the future, a potential error may cause the program to stop unexpectedly.

Summary

  • Inside the promise, the catch() method will catch the error caused by the throw statement and reject().
  • If an error occurs and you don’t have the catch() method, the JavaScript engine issues a runtime error and stops the program.

Was this tutorial helpful ?

dbigpot

dbigpot

Posted on Oct 11, 2019

• Updated on Oct 16, 2019

Error handling in JavaScript can be easy while being tricky at certain places, especially Promises. JS allows error handling with the help of try, catch, and throw.

const main = () => {
  try {
    // Do something super crazy
    if (!bakePizza()) {
      throw new Error('Oh no!');
    }
  } catch (error) {
    // That escalated quickly
    handleOvenExplosion();
  }
}

This seems simple enough but gets a little tricky when Promises are involved.

Let’s look at a simple example of a Promise. The following Promise function fetches a list of user profiles from the database, where the result set is resolved by the promise function and the error is rejected.

const userProfileQuery = new Promise((resolve, reject) => {
  connection.query('SELECT * FROM Users', [], (err, result) => {
    if (err) reject({ type: 'SQL', err});
    connection.release();
    resolve(result);
});
userProfileQuery
  .then((data) => {
    const userList = data;
    // Do something crazy with the list
  })
  .catch((err) => {
    // Oh, snap!
    // Handle error
  });

In an ideal world, we’d want to have a single try-catch block to handle all errors that occur in that single file.

const { getUserProfiles } = require('./helpers');
module.exports = () => {
  try {
    let userProfileList;
    getUserProfiles
      .then((data) => {
        userProfileList = data;
      })
      .catch((error) => {
        // Handle Promise Error
        // All errors thrown in this promise land here
      });
  } catch (error) {
    // Handle errors in this module
  }
}

The above module is simple — It fetches a list of user profiles with the help of a Promise function.

But the problem with the above module is that when we throw a new Error inside the then block of the promise, it will always pass to the catch block of the promise. That is because throwing a new error inside a then block of a promise will always be passed to the catch block of the invoking promise function. This does not allow us to handle all errors in a module with a singular try-catch block.

But, alas! There is a way to handle this with the help of Async/Await. Let me explain this better with an example —

const { getUserProfiles } = require('./helpers');
module.exports = async () => {
  try {
    const userProfileList = await getUserProfiles;
  } catch (error) {
    // Handle errors in this module
    switch (type) {
      case ERROR_SQL:
        // Handle SQL errors
      default:
        // Handle common errors
    }
  }
}

This little addition of async/await in your code does two things —
Assign the value to the variable which was resolved by the promise function.

Throw error if the promise function rejects anything.
Note that the value assignment works only when a promise functions resolves some value and errors get thrown only when the promise function rejects something.

This way, async/await lets us keep our code clean, maintainable, and easy to read.


Thanks for reading. If you have thoughts on this, be sure to leave a comment.

JavaScript promises allow asynchronous code to use structured error handling. The promise chains are served as a great way of error handling.

Whenever a promise rejects, the control jumps to the nearest rejection handler.
One of the most useful methods of error-handling is .catch.

As it was already mentioned, .catch is one of the most useful methods for error handling in JavaScript.

Let’s view a case, in which the URL to fetch is wrong (no such site), and the .catch method handles it:

w3docs logo
Javascript promise fetch is wrong and .catch method

fetch(‘https://noSuchServer.someText’) // rejects
.then(response => response.json())
.catch(err => console.log(err)) // TypeError: failed to fetch (the text may vary)

But note that .catch is not immediate. It might appear after one or several .then.

Everything might be correct with the site, but the response is not valid JSON.
The simplest way of catching all the errors is to append .catch to the end of the chain. Here is an example:

w3docs logo
Javascript promise fetch is wrong and .catch method

fetch(‘/promiseChaining/user.json’)
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
.then(user => new Promise((resolve, reject) => {
let img = document.createElement(‘img’);
img.src = user.avatarUrl;
img.className = «promiseAvatarExample»;
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(user);
}, 3000);
}))
.catch(error => console.log(error.message));

Usually, .catch doesn’t trigger. But, in case any of the promises, as mentioned earlier, rejects, it will catch it.

There is an invisible try..catch around the code of a promise handler and promise executor. In case of an exception, it will be treated as a rejection.

It is shown in the following code:

new Promise((resolve, reject) => {
  throw new Error("Error!!");
}).catch(console.log); // Error: Error!!

It operates the same as this code:

w3docs logo
Javascript try..catch

new Promise((resolve, reject) => {
reject(new Error(«Error!!»));
}).catch(console.log); // Error: Error!!

The invisible try..catch will catch the error and transform it into a rejected promise. It can happen both in the executor function and its handlers. In case you throw inside a .then handler, it means a rejected promise, and the control jumps to the closest error handler.

An example will look like this:

w3docs logo
Javascript try..catch

new Promise((resolve, reject) => {
resolve(«Yes»);
}).then((result) => {
throw new Error(«Error!!»); // rejects the promise
}).catch(console.log); // Error: Error!!

That may happen to all the errors, not just ones caused by the throw statement.

We can consider a programming error as an example:

w3docs logo
Javascript throw statement

new Promise((resolve, reject) => {
resolve(«Yes»);
}).then((result) => {
someFunction(); // no such function
}).catch(console.log); // ReferenceError: someFunction is not defined

The final .catch will catch both explicit rejections and accidental errors in the handlers.

As it was already stated, .catch at the end of the promise chain is equivalent to try..catch. You may have as many .then handlers as you like, then use a single .catch at the end of the chain for handling all the errors.

The regulartry..catch allows you to analyze an error and rethrow it if it can’t be handled. A similar situation is possible for promises.

If you throw inside .catch, then the control will go to the next nearest error handler. If you handle the error and finish normally, it will continue to the next nearest successful .then handler.

The example below illustrates the successful error-handling with .catch:

w3docs logo
Javascript error-handling with .catch

// the execution: catch -> then
new Promise((resolve, reject) => {
throw new Error(«Error!!»);
}).catch(function (error) {
console.log(«The error-handling, continue normally»);
}).then(() => console.log(«The next successful handler runs»));

In the example above, the .catch block finishes normally. Hence, the next effective .then is called.

Now let’s check out another situation with .catch. The handler (*) catches the error but is not capable of handling it:

w3docs logo
Javascript error-handling with .catch console.log

// the execution: catch -> catch -> then
new Promise((resolve, reject) => {
throw new Error(«Error!!»);
}).catch(function (error) { // (*)
if (error instanceof URIError) {
// handle
} else {
console.log(«Can’t handle such a error»);
throw error; // throwing this or that error jumps to the next catch
}
}).then(function () {
/* doesn’t run here */
}).catch(error => { // (**)
console.log(`The unknown error: ${error}`);
// do not return anything => execution goes the usual way
});

The execution jumps from the initial .catch (*) to the following one down the chain.

In this section, we will examine the cases when errors are not handled.

Let’s see that you have forgotten to append .catch to the end of the chain.

Here is an example:

new Promise(function () {
    noSuchFunc(); // Error here, no such function
  })
  .then(() => {
    // successful promise handlers
  }); // no append .catch at the end

If there is an error, the promise will be rejected. The execution will jump to the nearest rejection handler. But there exists none, and the error will get “stuck”.There isn’t any code for handling it.

So, what will happen if an error is not caught by try..catch? The script will collapse with a console message. Things like that occur with unhandled promise rejections.

The engine of JavaScript usually tracks this kind of rejections, generating a global error.

For catching such errors in the browser, you can use the event unhandledRejection, as follows:

window.addEventListener('unhandledRejection', function (event) {
  // the event object has two special properties
  console.log(event.promise); // [object Promise] - error
  console.log(event.reason); // Error: Error!! - the unhandled error
});
new Promise(function () {
  throw new Error("Error!!");
}); // no catch to handle the error

So, in case there is an error, and no .catch can be found, the unhandledRejection will trigger getting the event object with the information regarding the error.

As a rule, this kind of errors are unrecoverable. The most proper solution in such circumstances is to inform the user about it, reporting the incident to the server.

Non-browser environments, such as Node.js, include other options for tracking unhandled errors.

One of the most significant assets of using promises is the way they allow you to handle errors.

Errors in the promises can be handled with .catch: no matter it’s a reject() call or an error thrown in a handler. It would be best if you put .catch precisely in the places where you want to handle errors. The handler analyzes the errors rethrowing the ones that are unknown (for example, programming mistakes).

In any other case, you need to have unhandledRejection event handler ( for browsers and analogs of different environments). It will track unhandled errors informing the user about them. It will help you avoid the collapse of your app.

Последнее обновление: 30.08.2021

Одним из преимуществ промисов является более простая обработка ошибок. Для получения и обработки ошибки мы можем использовать
функцию catch() объекта Promise, которая в качестве параметра принимает функцию обработчика ошибки:

const myPromise = new Promise(function(resolve, reject){
	console.log("Выполнение асинхронной операции");
	reject("Переданы некорректные данные");
});
myPromise.catch( function(error){
	console.log(error);
});

Функция catch() в качестве параметра принимает обработчик ошибки. Параметром этой функции-обработчика является то значение,
которое передается в reject().

Консольный вывод:

Выполнение асинхронной операции
Переданы некорректные данные

Генерация ошибки

Выше для извещения о возникшей ошибке вызывалась функция reject(). Но стоит отметить, что ошибка может возникнуть и без вызова функции
reject(). И если в выполняемой в промисе операции генерируется ошибка в силу тех или иных причин, то вся операция также завершается ошибкой.
Например, в следующем коде вызывается нигде не определенная функция getSomeWork():

const myPromise = new Promise(function(resolve){
	console.log("Выполнение асинхронной операции");
	getSomeWork();		// вызов не существующей функции
	resolve("Hello world!");
});
myPromise.catch( function(error){
	console.log(error);
});

Поскольку функция getSomeWork() нигде не объявлена, то выполнение асинхронной задачи завершится ошибкой и не дойдет до вызова resolve("Hello world!").
Поэтому сработает функция обработки ошибок из catch(), которая через параметр error получит информацию о возникшей ошибке, и
в консоли браузера мы увидим сообщение об ошибке:

Выполнение асинхронной операции
ReferenceError: getSomeWork is not defined
    at index.html:39
    at new Promise (<anonymous>)
    at index.html:37

throw

Также ошибка может быть результатом вызова оператора throw, который генерирует ошибку:

cconst myPromise = new Promise(function(resolve, reject){
	console.log("Выполнение асинхронной операции");
	const parsed = parseInt("Hello");
	if (isNaN(parsed)) { 
		throw "Not a number";			// Генерируем ошибку
	}
	resolve(parsed);
});
myPromise.catch( function(error){
	console.log(error);
});

Здесь парсится в число случайная строка. И если результат парсинга не представляет число, то с помощью оператора throw генерируем ошибку.
Это придет к завершению всей функции с ошибкой. И в итоге результат будет обработан функцией catch:

Выполнение асинхронной операции
Not a number

В этом случае функция обработчика получает сообщение об оошибке, который указывается после оператора throw.

Данная ситуация может показаться искуственной, так как нам нет смысла генерировать в коде выше ошибку с помощью throw, поскольку в этом случае мы также
можем передать сообщение об ошибке в функцию reject:

if (isNaN(parsed)) { 
	reject("Not a number");
}

Однако данный оператор может применяться во внешней функции, которую мы вызываем в коде:

function getNumber(str){
	const parsed = parseInt(str);
	if (isNaN(parsed)) throw "Not a number";			// Генерируем ошибку
	else return parsed;
}
const myPromise = new Promise(function(resolve){
	console.log("Выполнение асинхронной операции");
	const result = getNumber("hello");
	resolve(result);
});
myPromise.catch( function(error){
	console.log(error);
});

Здесь парсинг строки в число вынесен во внешнюю функцию — getNumber, однако при вызове этой функции в промисе, также из оператора throw возникнет ошибка.
И соответственно будет выполняться функция catch(), где роизойдет обработка ошибки.

try..catch

Как и в общем случае, операции, которые могут генерировать ошибку, можно помещать в конструкцию try..catch, а при возникновении исключения в блоке catch вызывать функцию reject():

const myPromise = new Promise(function(resolve, reject){
	try{
		console.log("Выполнение асинхронной операции");
		getSomeWork();		// вызов не существующей функции
		resolve("Hello world!");
	}
	catch(err){
		reject(`Произошла ошибка: ${err.message}`);
	}
});
myPromise.catch( function(error){
	console.log(error);
});

Консольный вывод:

Выполнение асинхронной операции
Произошла ошибка: getSomeWork is not defined

Обработка ошибки с помощью функции then

Кроме функции catch для получения информации об ошибке и ее обработки также можно использовать функцию
then() — ее второй параметр представляет обработчик ошибки, который в качестве параметра получает переданное из функции
reject значение:

promise
  .then(function(value){
    // получение значения
  },
  function(error){
    // обработка ошибки
  });
 

Второй параметр функции then() представляет функцию обработчика ошибок. С помощью параметра error в функции-обработчика мы можем получить переданное в reject() значение, либо информацию о возникшей ошибке.

Рассмотрим следуюший пример:

function generateNumber(str){ 
	return new Promise(function(resolve, reject){
		const parsed = parseInt(str);
		if (isNaN(parsed))	reject("значение не является числом")
		else resolve(parsed);
	})
	.then(function(value){ console.log("Результат операции:", value);}, 
		function(error){ console.log("Возникла ошибка:", error);});
}

generateNumber("23");
generateNumber("hello");

В данном случае для того, чтобы в промис можно было передать разные данные, он определен как возващаемый результат функции generateNumber(). То есть в данном случае консольный вывод будет следующим:

Результат операции: 23
Возникла ошибка: значение не является числом

Понравилась статья? Поделить с друзьями:
  • Throw error laravel
  • Throw error js что это
  • Throw error javascript new error
  • Throw error haskell
  • Throw er unhandled error event node js