Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid callback hell

I have this confirmation in AngularJS that takes two callbacks: one if the user clicked Yes and another one if the user clicked No on a dialog box.

I need to ask the user first if he is done with the operation and, if he replies yes, I need to also ask if he wants to add the user as a manager. Regardless of the response for the two questions, I need to persist the data.

So I have this horrible, callback hell infested code to handle the branches I mentioned above:

$scope.promptAndSave = function() {

  // first question
  $scope.confirmation.display("Confirmation", "Did you finish working on this Reference Check?", function() {

    // handles Yes for first question
    $scope.referenceCheck.finished = true;
    var manager_name = $scope.referenceCheck.first_name + " " + $scope.referenceCheck.last_name;
    $scope.confirmation.display("Add new Manager", "Do you want to add <b>" + manager_name + "</b> as a new manager to your database?", function() {

      // handles Yes for second question
      $scope.referenceCheck.add_manager = true;
      $scope.saveReferenceCheck();

    }, function() {

      // handles No for second question
      $scope.saveReferenceCheck();

    });
  }, function() {

    // handles No for first question
    $scope.saveReferenceCheck();

  });
};

Is this something that can potentially be resolved using promises? Or would any other, more generic JS technique that would fit this better?

I understand AngularJS provides $q, would that help here? How would the code above look when converted to promises?

EDIT

So confirmation is a local object on my $scope, like this:

$scope.confirmation = {
  visible: false,
  title: "",
  prompt: "",
  yes: function() {
    this.visible = false;
    this.yesHandler();
  },
  no: function() {
    this.visible = false;
    this.noHandler();
  },
  display: function(title, prompt, yesHandler, noHandler) {
    this.title = title;
    this.prompt = prompt;
    this.yesHandler = yesHandler;
    this.noHandler = noHandler;
    this.visible = true;
  }
};

And I have template HTML code that interacts with the object to display the prompt:

<div id="confirmationForm" ng-show="confirmation.visible">
  <div class="form-title">{{ confirmation.title }}</div>
  <div class="form-body">
    <div class="message" ng-bind-html-unsafe="confirmation.prompt"></div>

    <div class="actions">
      <span>
        <button ng-click="confirmation.yes()">Yes</button>
        <button ng-click="confirmation.no()">No</button>
      </span>
      <img src="/assets/spinner.gif" ng-show="confirmation.busy">
    </div>
  </div>
</div>

EDIT 2

With some help, I managed to put this together: http://jsfiddle.net/LJr5z/1/

EDIT 3

And to finish, $q doesn't support finally in Angular 1.0.3, so here's the JSFiddle for the last version, with AngularJS 1.2 - http://jsfiddle.net/LJr5z/3/

like image 282
kolrie Avatar asked Dec 27 '13 20:12

kolrie


1 Answers

So inject $q and try this.

$scope.confirmation = {
  visible: false,
  title: "",
  prompt: "",
  yes: function() {
    this.visible = false;
    this.dfd.resolve();
  },
  no: function() {
    this.visible = false;
    this.dfd.reject();
  },
  display: function(title, prompt) {
    this.dfd = $q.defer();
    this.title = title;
    this.prompt = prompt;
    this.visible = true;
    return this.dfd.promise;
  }
};

To call it:

$scope.confirmation.display('title', 'prompt')
.then(function() { 
    // return another promise here, yes clicked
}, function() {
    // no clicked
})
.then(...);
like image 65
Daniel A. White Avatar answered Oct 19 '22 11:10

Daniel A. White