Angular JS: Javascript promises

[Fuente:  http://wildermuth.com/2013/8/3/JavaScript_Promises]

En este artículo hablamos sobre el concepto de objecto promise que varias librerias Javascript utilizan (incluyendo AngularJS , jQuery , Dojo y WinJS).

Una promise es un patrón para manejar operaciones asíncronas. El problema es que esencialmente cuando comienzas una operación asíncrona, necesitas ejecutar cierto código cuando la operación se ha completado. El código asíncrono se ha hecho tan común que la mayoría de las librerias javascript han creado una solución pasar los callbacks Pero hay ciertas semejanzas en cómo lo hacen todas. Veamos jQuery como ejemplo:

var $info = $("#info");

$.ajax({
    url:"/echo/json/",
    data: { json: JSON.stringify({"name": "someValue"}) },
    type:"POST",
    success: function(response)
    {
       $info.text(response.name);
    }
});

En este ejemplo puedes ver que jQuery utiliza la propiedad “success” de los settings para especificar el callback. Esto no es un promise aunque sí es una forma de pasar funciones callback. When the ajax call is complete, it calls the success function. Depending on the library that uses asynchronous operations, you might pass in a set of callbacks (e.g. for success or failure). There are a ton of ways to accomplish this.

El patrón promise intenta simplificar este proceso. La operación asíncrona simplemente retorna un objeto llamado una promise.Este objeto promise te permite invocar un método llamado “then” que te permite especificar las funciones a utilizar como callbacks. Veamos como consumir una promise utilizando jQuery como ejemplo:

var $info = $("#info");

$.ajax({
    url: "/echo/json/",
    data: {
        json: JSON.stringify({
            "name": "someValue"
        })
    },
    type: "POST"
})
.then(function (response) {
    $info.text(response.name);
});

Lo que es interesante aquí , es que el objeto que ajax retorna es el objeto xhr que implementa el patrón promise de forma que podemos invocar then como se ha mostrado. El poder de invocar a then es que puedes encadenar varias operaciones y completar la operación invocando “done” como mostramos en el siguiente ejemplo:

var $info = $("#info");

$.ajax({
    url: "/echo/json/",
    data: {
        json: JSON.stringify({
            "name": "someValue"
        })
    },
    type: "POST"
})
.then(function (response) {
    $info.text(response.name);
})
.then(function () {
    $info.append("...More");
})
.done(function () {
    $info.append("...finally!");
});

Because many libraries are starting to take on the promise pattern, handling asynchronous operations should be easier no matter what code you’re writing (e.g. NodeJS, in-browser JS, etc.). But what does a promise look like from the other side?

One important key to the pattern is that the then function can accept two functions. The first is for the success callback; the second for the failure callback like so:

var $info = $("#info");

$.ajax({
    // Change URL to see error happen
    url: "/echo/json/",
    data: {
        json: JSON.stringify({
            "name": "someValue"
        })
    },
    type: "POST"
})
.then(function (response) {
    // success
    $info.text(response.name);
}, 
function () {
    // failure
    $info.text("bad things happen to good developers");
})
.always(function () {
    $info.append("...finally");
});

Notice that in jQuery we’re using a call to always to specify that we want to be called whether the success or failure was called.

Let’s see how using a promise looks. Here is an example from AngularJS:

var m = angular.module("myApp", []);

m.factory("dataService", function ($q) {
    function _callMe() {
        var d = $q.defer();

        setTimeout(function () {
            d.resolve();
            //defer.reject();
        }, 100);

        return d.promise;
    }

    return {
        callMe: _callMe
    };
});

function myCtrl($scope, dataService) {
    $scope.name = "None";
    $scope.isBusy = true;
    dataService.callMe()
      .then(function () {
        // Successful
        $scope.name = "success";
      }, 
      function () {
        // failure
        $scope.name = "failure";
      })
      .then(function () {
        // Like a Finally Clause
        $scope.isBusy = false;
      });
}

AngularJS uses an implementation (see the $q variable) that is started with a call to defer() This returns an object that contains ways to mark a successful or failure condition as well as the promise itself. Notice that in the _callMe function the variable is created by calling $q.defer() then the d.promise is returned from the function so that the caller can call the promise methods (e.g. then). When the actual asynchronous operation is performed (in this case mocked up as a setTimeout call), we can use the resolve method on the defer’d object to tell the promise that we completed successfully (and therefore call the first function in the then method below). If we were to call reject, the second method (the failure call) would be called instead.

You can play with these examples in JSFiddle and see what you can make happen. Promises are a really simple and cool way to handle asynchronicity. What I really like about it is that it simplifies your code (so that you don’t have the triangle of doom when you have to nest callback functions inside each other. This makes it easy.