How to properly catch errors in promises?

2020-03-24 07:20发布

I am under the impression that these two things are not equivalent:

return somePromise()
  .then()
  .then()
  .then()
  .catch(function(e) { throw e; });

and

return somePromise()
  .catch(function(e) { throw e; });
  .then()
  .catch(function(e) { throw e; });
  .then()
  .catch(function(e) { throw e; });
  .then()
  .catch(function(e) { throw e; });

The first snippet will only catch errors on the latest then (the other errors will be lost) whereas the second snippet will catch any error anywhere along the chain.

But I must be missing something because forcing the user to remember putting a catch after every promise defeats the purpose of promises.

Am I misunderstanding and placing a .catch() last will catch any error along the chain?

3条回答
在下西门庆
2楼-- · 2020-03-24 07:35

In the first case, if any of the then handlers are throwing an error, then that error will be caught at the last catch handler.

In the second case, if any of the then handlers throw an error, that error will go to the nearest catch handler. As you throw the error in catch handlers as well, it will simply go to the next nearest catch handler.

Am I misunderstanding and placing a .catch() last will catch any error along the chain?

Nope, catch at the end of the promise chain is the right thing to do in most of the cases. But, in case if you don't want to fail the entire chain of promises because of an intermediate failure, then you can return the value to be used by the next then handler in the catch handler.

查看更多
别忘想泡老子
3楼-- · 2020-03-24 07:45

Some samples to have a better understanding of errors with promises.

You have to run the snippet and compare the result with the code to understand what happen.

In this sample we have :

  • 4 tests where we have a Promise chain in which we throw errors.
  • 4 ways to handle errors.

This sample try to show 4 different ways to handle the errors, and the result.

I hope this can help someone !

/*
* This part is only utility functions
* dont care about that
*/
var el = $('#dbg');
var fn = {
    log: function(val ) {
      el.append('<pre class="line">' + val + '</pre>');
      return val;
    },
    err : function(val, forced){
      var errNumber = forced ? val : 404;
      fn.log('<div class="thr">throwing an error : ' + errNumber + '</div>' );
      throw errNumber;
    },
    ok: function(val) {
      fn.log('<div class="ok">received : ' + val + ' | returning : ' + (val+1) + '</div>');
      return val+1;
    },
    ko: function(val) {
      fn.log('<div class="ko">received : ' + val + ' | returning : ' + (val-1) + '</div>');
      return val-1;
    },
    catch : function(val){
      fn.log('<div class="ko">FROM CATCH : \treceived : ' + val + ' | returning : ' + val + '</div>');
      return val;
    
    },
    sep : function(val){
      fn.log('<div class="sep">&nbsp;</div>');
      return val;
    
    },
    
    };
                

fn.log('Each fn.ok increment + 1 => fn.ok(5) : log 5 in a green line and return 6');
fn.ok(5);
fn.log('');
fn.log('Each fn.ko decrement - 1 => fn.ko(5) : log 5 in a red line and return 4');
fn.ko(5);




/*
*
* Each fn.ok increment + 1
* Each fn.ko decrement - 1
*
*/


/*
* Test 1 :
*
*    only one catch at end
*
*/
var p = Promise.resolve()
  .then(function(){
    
    var val = 1;
    
    fn.sep();
    fn.log('start test : ' + val);
    fn.log('\n\tonly one catch at end\n<hr>');
    fn.log('Promise.resolve(1)\n\t.then(fn.ok)\n\t.then(fn.ok)\n\t.then(fn.ok)\n\t  .then(fn.err)\n\t.then(fn.ok)\n\t.then(fn.ok)\n\t.then(fn.ok)\n\t.catch(fn.catch)');
    
    return val;
  })
  .then(fn.ok)
  .then(fn.ok)
  .then(fn.ok)
    .then(fn.err)
  .then(fn.ok)
  .then(fn.ok)
  .then(fn.ok)
  .catch(fn.catch)
  ;

/*
* Test 2 :
*
*    same as test 1
*    only one catch at end
*    but we start by an error
*
*/
p = p.then(function(){
    
    var val = 2;  
    
    fn.sep();
    fn.log('start test : ' + val);
    fn.log('\n\tsame as test 1\n\tonly one catch at end\n\tbut we start by an error\n<hr>');
    fn.log('Promise.resolve()\n\t  .then(fn.err)\n\t.then(fn.ok)\n\t.then(fn.ok)\n\t.then(fn.ok)\n\t  .then(fn.err)\n\t.then(fn.ok)\n\t.then(fn.ok)\n\t.then(fn.ok)\n\t.catch(fn.catch)');
 
    
    return fn.err();
  })
  .then(fn.ok)
  .then(fn.ok)
  .then(fn.ok)
    .then(fn.err)
  .then(fn.ok)
  .then(fn.ok)
  .then(fn.ok)
  .catch(fn.catch)
  ;

/*
* Test 3 :
*
*    same as test 2
*    we start by an error
*    but each one is chained
*    to a catcher
*
*/
p = p.then(function(){
    
    var val = 3;  
    
    fn.sep();
    fn.log('start test : ' + val);
    
    fn.log('\n\tsame as test 2\n\twe start by an error\n\tbut each one is chained\n\tto a catcher\n<hr>');
    fn.log('Promise.resolve('+val+')\n\t  .then(fn.err)\n\t.then(fn.ok).catch(fn.catch)\n\t.then(fn.ok).catch(fn.catch)\n\t.then(fn.ok).catch(fn.catch)\n\t  .then(fn.err)\n\t.then(fn.ok).catch(fn.catch)\n\t.then(fn.ok).catch(fn.catch)\n\t.then(fn.ok).catch(fn.catch)\n\t.catch(fn.catch)');  
  
    return fn.err(val , true);
  })
  .then(fn.ok).catch(fn.catch)
  .then(fn.ok).catch(fn.catch)
  .then(fn.ok).catch(fn.catch)
    .then(fn.err)
  .then(fn.ok).catch(fn.catch)
  .then(fn.ok).catch(fn.catch)
  .then(fn.ok).catch(fn.catch)
  .catch(fn.catch)
  ;

/*
* Test 4 :
*
*    same as test 2
*    we start by an error
*    but each one have
*    a rejected handler
*
*/

p = p.then(function(){
    
    var val = 4;  
    
    fn.sep();
    fn.log('start test : ' + val);
    fn.log('\n\tsame as test 2\n\twe start by an error\n\tbut each one have\n\ta rejected handler\n<hr>');

    fn.log('Promise.resolve('+val+')\n\t  .then(fn.err)\n\t.then(fn.ok , fn.ko)\n\t.then(fn.ok , fn.ko)\n\t.then(fn.ok , fn.ko)\n\t  .then(fn.err , fn.ko)\n\t.then(fn.ok , fn.ko)\n\t.then(fn.ok , fn.ko)\n\t.then(fn.ok , fn.ko)\n\t.catch(fn.catch)');
  
    return fn.err(val , true);
  })
  .then(fn.ok , fn.ko)
  .then(fn.ok , fn.ko)
  .then(fn.ok , fn.ko)
    .then(fn.err , fn.ko)
  .then(fn.ok , fn.ko)
  .then(fn.ok , fn.ko)
  .then(fn.ok , fn.ko)
  .catch(fn.catch)
  ;
.line{
  border:solid 1px transparent;
  margin : 3px;
  padding : 3px;
  }
.line .ok,
.line .ko,
.line .thr{
  margin-left : 24px;
  padding-left : 3px;
  }
.ok{
  background : #9F9;
  }
.ko{
  background : #F99;
  }
.thr{
  background : #666;
  color : #DDD;
  }
.sep{
  border:solid 1px #666;
  background : #CCF;
  border-radius : 12px;
  margin-top : 21px;
  }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://raw.githubusercontent.com/jakearchibald/es6-promise/master/dist/es6-promise.min.js"></script>


<div id='dbg'></div>

查看更多
家丑人穷心不美
4楼-- · 2020-03-24 07:53

Just adding to thefourtheye's great answer. Here is how your two code snippets look in synchronous code:

return somePromise()
  .then()
  .then()
  .then()
  .catch(function(e) { throw e; });

Becomes:

try {
    var val = someFunction();
    val = fn(val); // p.then(x => ...);
    //...
    return val;
} catch (e) {
    throw e; // this catch didn't actually do anything, but will be reached if someFunction
            // throws an error during execution
}

The second example:

return somePromise()
  .catch(function(e) { throw e; });
  .then()
  .catch(function(e) { throw e; });
  .then()
  .catch(function(e) { throw e; });
  .then()
  .catch(function(e) { throw e; });

Becomes, the not very interesting:

try { 
try {
   var val = someFunction();
   return val;
} catch (e) {
   throw e;
}
val = fn(val); // this is a then(); If it's really an empty then - a noop
} catch (e) {
   throw e;
}
val = fn(val); // this is a then(); If it's really an empty then - a noop
} catch (e) {
   throw e;
}
val = fn(val); // this is a then(); If it's really an empty then - a noop
} catch (e) {
   throw e; // yep, even this last one will be reached
}
}
查看更多
登录 后发表回答