Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unhandled promise rejection in asynchronous promises

I am using Oboe.js to parse a really really large JSON file

const promises = [];
oboe('http://domain/my-file.js')
  .node('items.*', item => {
    // parseItem() returns a rejected Promise because of invalid JSON items
    promises.push(parseItem(item));
  })
  .done(() => {
    Promise.all(promises).then(() => {
      doSomething();
    });
  })

But my Browser console gets flooded with Uncaught (in promise). The same occurs if you write a promise in a setTimeout() like

const promises = [];
setTimeout(() => {
  promises.push(Promise.reject());
}, 500);
// some time in the future
Promise.all(promises);

What's really strange: modern browsers behave differently. In Firefox Developer Edition everything works without the error messages and in Chrome I get flooded with Uncaught (in promise). In Chrome you get the message instantly if you write Promise.reject(); without a catch. In Firefox and Safari nothing happens.

So what's the solution for this? Ignoring the message? I mean if this behavior is really in the official promise spec then promises in asynchronous code does not make really sense for me.

like image 921
LongFlick Avatar asked Oct 30 '22 17:10

LongFlick


1 Answers

Your Oboe issue is based on the fact Oboe streams JSON in, so we never know in advance how many promises there are so we can't attach the responsibility to Promise.all in advance. We can "promisify" Oboe to be able to return promises in its methods.

Typically, I'd use RxJS to automatically create a stream from the event emitter -and RxJS's methods can already return promises and then aggregate it. However - since I don't want a third party library here, and it has less teaching value - let's implement it ourselves:

function addMap(oboe) { 
  oboe.map = function(selector, mapper){
    var promises = [];
    return new Promise(function(resolve, reject){ // create a new promise
      oboe.node(selector, function(match){
        var result = mapper(match); // get result
        // signal that we're handling the rejection to make sure it's not handled.   
        result.catch(function(){});
        promises.push(result);
      });
      oboe.fail(reject); 
      oboe.done(function(){ resolve(promises); });
   });
  };
}

Which would let us do:

var o = oboe("foo");
addMap(o);
o.map("items.*", item => downloadItem(item)).then(result => {
   // handle result here
}); 

Your setTimeout issue is very contrived. The vast majority of people do not write code that looks like this in practice - in practice adding a error handler asynchronously is a pretty rare use case when not working against an API that forces you to do so (like the Oboe.js example).

What's really strange: modern browsers behave differently

This is because Firefox uses GC for detecting unhandled rejections and Chrome a timer. It's implementation detail - the only guarantee you'll have is that errors will not be logged if attached within a microtask (synchronously, or in a then that executes on the same turn).

like image 89
Benjamin Gruenbaum Avatar answered Nov 15 '22 05:11

Benjamin Gruenbaum