Break promise chain and call a function based on t

2019-01-03 08:03发布

Update:

To help future viewers of this post, I created this demo of pluma's answer.

Question:

My goal seems fairly straightforward.

  step(1)
  .then(function() {
    return step(2);
  }, function() {
    stepError(1);
    return $q.reject();
  })
  .then(function() {

  }, function() {
    stepError(2);
  });

  function step(n) {
    var deferred = $q.defer();
    //fail on step 1
    (n === 1) ? deferred.reject() : deferred.resolve();
    return deferred.promise;
  }
  function stepError(n) {
    console.log(n); 
  }

The problem here is that if I fail on step 1, both stepError(1) AND stepError(2) are fired. If I don't return $q.reject then stepError(2) won't be fired, but step(2) will, which I understand. I've accomplished everything except what I'm trying to do.

How do I write promises so that I can call a function on rejection, without calling all of the functions in the error chain? Or is there another way to accomplish this?

Here's a live demo so you've got something work with.

Update:

I kind of have solved it. Here, I am catching the error at the end of the chain and passing the data to reject(data) so that I will know what issue to handle in the error function. This actually doesn't meet my requirements because I don't want to depend on the data. It would be lame, but in my case it would be cleaner to pass an error callback to the function rather than to depend on the returned data to determine what to do.

Live demo here (click).

step(1)
  .then(function() {
    return step(2);
  })
  .then(function() {
    return step(3);
  })
  .then(false, 
    function(x) {
      stepError(x);
    }
  );
  function step(n) {
    console.log('Step '+n);
    var deferred = $q.defer();
    (n === 1) ? deferred.reject(n) : deferred.resolve(n);
    return deferred.promise;
  }
  function stepError(n) {
    console.log('Error '+n); 
  }

9条回答
爷的心禁止访问
2楼-- · 2019-01-03 08:40

What you need is a repeating .then() chain with a special case to start and a special case to finish.

The knack is to get the step number of the failure case to ripple through to a final error handler.

  • Start: call step(1) unconditionally.
  • Repeating pattern: chain a .then() with the following callbacks:
    • success: call step(n+1)
    • failure: throw the value with which the previous deferered was rejected or rethrow the error.
  • Finish: chain a .then() with no success handler and a final error handler.

You can write the whole thing out longhand but it's easier to demonstrate the pattern with named, generalised functions :

function nextStep(n) {
    return step(n + 1);
}

function step(n) {
    console.log('step ' + n);
    var deferred = $q.defer();
    (n === 3) ? deferred.reject(n) : deferred.resolve(n);
    return deferred.promise;
}

function stepError(n) {
    throw(n);
}

function finalError(n) {
    console.log('finalError ' + n);
}
step(1)
    .then(nextStep, stepError)
    .then(nextStep, stepError)
    .then(nextStep, stepError)
    .then(nextStep, stepError)
    .then(nextStep, stepError)
    .then(null, finalError);});

see demo

Note how in step(), the deferred is rejected or resolved with n, thus making that value available to the callbacks in the next .then() in the chain. Once stepError is called, the error is repeatedly rethrown until it is handled by finalError.

查看更多
啃猪蹄的小仙女
3楼-- · 2019-01-03 08:43

Bit late to the party but this simple solution worked for me:

function chainError(err) {
  return Promise.reject(err)
};

stepOne()
.then(stepTwo, chainError)
.then(stepThreee, chainError);

This allows you to break out of the chain.

查看更多
做自己的国王
4楼-- · 2019-01-03 08:48

Found Promise.prototype.catch() examples on MDN below very helpful.

(The accepted answer mentions then(null, onErrorHandler) which is basically the same as catch(onErrorHandler).)

Using and chaining the catch method

var p1 = new Promise(function(resolve, reject) {
  resolve('Success');
});

p1.then(function(value) {
  console.log(value); // "Success!"
  throw 'oh, no!';
}).catch(function(e) {
  console.log(e); // "oh, no!"
}).then(function(){
  console.log('after a catch the chain is restored');
}, function () {
  console.log('Not fired due to the catch');
});

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

Gotchas when throwing errors

// Throwing an error will call the catch method most of the time
var p1 = new Promise(function(resolve, reject) {
  throw 'Uh-oh!';
});

p1.catch(function(e) {
  console.log(e); // "Uh-oh!"
});

// Errors thrown inside asynchronous functions will act like uncaught errors
var p2 = new Promise(function(resolve, reject) {
  setTimeout(function() {
    throw 'Uncaught Exception!';
  }, 1000);
});

p2.catch(function(e) {
  console.log(e); // This is never called
});

// Errors thrown after resolve is called will be silenced
var p3 = new Promise(function(resolve, reject) {
  resolve();
  throw 'Silenced Exception!';
});

p3.catch(function(e) {
   console.log(e); // This is never called
});

If it is resolved

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

var p2 = p1.catch(function (reason) {
    //This is never called
    console.log("catch p1!");
    console.log(reason);
});

p2.then(function (value) {
    console.log("next promise's onFulfilled"); /* next promise's onFulfilled */
    console.log(value); /* calling next */
}, function (reason) {
    console.log("next promise's onRejected");
    console.log(reason);
});
查看更多
登录 后发表回答