Chaining Angular promise rejections

2020-05-03 10:06发布

I have a chained promise and in the case of a rejection for either of the promises, I need to perform an async operation (get the translated error message). As I've already got a chained promise on success, I assume it's not possible to also chain on rejection - I am attempting to simply nest the async calls, but I'm not getting the resolved promise back from deferred.reject(deferredRejection.promise); below. Pointers appreciated!

login: function(email, password) {
  var deferred = $q.defer();
  AuthService.login(email, password).then(function(response) {
    var user = {
      'accountToken': response.accountToken,
      'email': response.username,
      'onboarded': response.onboarded,
      'verified': response.verified
    };          
    return SyncStorageService.write(SyncStorageService.storageKeys.user, 
        user);  
  }, function(error) {
    // login failed
    var deferredRejection = $q.defer();
    $translate('ALERTS.LOGIN_FAILED').then(function(translatedValue) {
      deferredRejection.resolve(translatedValue);
    });
    deferred.reject(deferredRejection.promise);
  }).then(function(data) {
    deferred.resolve(data);
  }, function(error) {
    // saving data failed
    var deferredRejection = $q.defer();
    $translate('ALERTS.UNKNOWN').then(function(translatedValue) {
      deferredRejection.resolve(translatedValue);
    });
    deferred.reject(deferredRejection.promise);
  });
  return deferred.promise;
}

Updated Solution:

Based on the answer below, I was able to re-write the code as follows:

login: function(email, password) {
  return AuthService.login(email, password).then(function(response) {
    return {
      'accountToken': response.accountToken,
      'email': response.username,
      'onboarded': response.onboarded,
      'verified': response.verified
    };
  }).then(function(data) {
    return SyncStorageService.write(SyncStorageService.storageKeys.user, 
        data);
  });
}

Notes:

  • Both AuthService.login and SyncStorageService.write now reject promises with an Error object (e.g. new Error('ALERT.ERROR_MESSAGE');), which bubbles up through login to the controller (previously was doing the translation at the service level);
  • The controller that calls the login method has .then() and .catch() blocks - on a .catch(), the passed Error.message is translated and displayed.

1条回答
Summer. ? 凉城
2楼-- · 2020-05-03 10:17

It looks like you're not really chaining promises, and using the forgotten promise/deferred anti pattern. Making a few assumptions about how you actually want it to behave, and factoring out the calls to $translate, then something like the following I suspect is what you're after:

login: function(email, password) {
  return AuthService.login(email, password).then(function(response) {
    return {
      'accountToken': response.accountToken,
      'email': response.username,
      'onboarded': response.onboarded,
      'verified': response.verified
    };          
  }, function() {
    return $q.reject('ALERTS.LOGIN_FAILED');
  }).then(function(user) {
    return SyncStorageService.write(SyncStorageService.storageKeys.user, user).catch(function() {
      return $q.reject('ALERTS.UNKNOWN');
    });
  }).catch(function(message) {
    return $translate(message).then(function(translatedValue) {
      return $q.reject(translatedValue);
    });
  });
}

The main things to keep in mind are:

  • If you definitely want to reject the derived promise, return $q.reject(error) from the success or error callback.

    All the error callbacks above do this. The ones after attempted login or saving use the translation key as the error that will be eventually passed to the final catch callback. The success callback from $translate also does this to transform its resolved promise to a rejected one, so the final catch callback returns a rejected promise, so the calling code gets a rejected promise, and (presumably) shows the translated error to the user.

  • If you definitely want to resolve the derived promise, return anything that isn't a promise from he success or error callbacks. The derived promise will be resolved with that value. (This includes undefined if you don't have an explicit return value).

    This is what is done above when returning the user return {'accountToken'.... in the first callback.

  • If you want to defer the resolution/rejection of a promise, return another promise from the success or error callback. Then the derived promise will be eventually resolved or rejected in whatever manner this other promise is resolved/rejected.

    This is what's done above when returning SyncStorageService.write..., and when returning $translate(....

查看更多
登录 后发表回答