So you write a model hook. You fetch some data. It
works beautifully. You feel like a genius. Then
something breaks — a dodgy response, a missing
property, an exception buried six layers deep in a
then chain — and your Ember app
just… does nothing. No error. No stack trace. The page
sits there half-rendered like a website having an
existential crisis. Brilliant.
The villain here is RSVP, the promise library Ember uses under the hood. RSVP dutifully follows the Promises/A+ spec, which — in its infinite wisdom — says that if a promise is rejected and nobody's listening, the error should just be quietly thrown in the bin. Not logged. Not surfaced. Binned. That's not a bug, that's the spec working as designed: unhandled rejections vanish into the void.
What this means in the real world is that any uncaught
exception inside a promise chain — a typo, a null
reference, a failed assertion — produces
absolutely zero output. Nothing. You're flying blind.
So you do what any rational person does: you start
plastering console.log statements
everywhere like crime scene tape, toggling breakpoints,
muttering profanities, and slowly losing the will to
live while trying to figure out which promise ate your
error for lunch. You can easily burn an hour on this
before you realize you've been debugging the wrong
bloody layer the entire time.
The fix
Here's the thing though — RSVP actually has a
global event hook for exactly this situation. It's been
sitting there the whole time, waiting for you to notice
it, like a fire extinguisher you walk past every day.
You subscribe to an 'error' event that
fires whenever a promise is rejected and nobody catches
it. Stick it in an Ember initializer, and suddenly every
swallowed error shows up in your console like it always
should have:
// app/initializers/rsvp-error-reporting.js
import Ember from 'ember';
export default {
name: 'ember-rsvp-error-reporting',
initialize: function (container, application) {
Ember.RSVP.on('error', function(reason) {
console.group('Ember.RSVP error:')
console.info(reason);
console.groupEnd();
});
}
};
That's it. That's the whole thing. The initializer runs
once when your Ember app boots, calls
Ember.RSVP.on('error', ...) to register a
global listener, and then just sits there doing its job
like a responsible adult. Whenever a promise rejects
without a handler, RSVP fires the callback with the
rejection reason — typically an Error
object with a message and stack trace. The
console.group wrapper keeps it all neatly
collapsed in DevTools so your console doesn't look like
someone vomited stack traces all over it.
Without this fix, a failing API call in a route's
model hook gives you a blank page and a
spotless console — the debugging equivalent of a
crime scene that's been wiped clean. With it, you get a
grouped log entry with the full error and stack trace
pointing straight at the problem like a helpful witness.
The difference is between spending five
seconds reading an error message and spending an hour
guessing.
Drop this file into any Ember CLI project, forget it exists, and get on with your life. Honestly, the fact that this isn't in every Ember app's boilerplate from day one is a minor tragedy of the JavaScript ecosystem.
- Register an
Ember.RSVP.on('error', ...)listener in an initializer - Every swallowed promise rejection becomes immediately visible in the console
- Add this to every Ember project from day one — it costs nothing and saves hours