How to actually learn how to use promises in javascript?

45

I've been trying to learn how to use promises and asynchronous programming in javascript for a while, but so far I have not had much success with it. I have already understood that there are functions that return such promises and that a callback can then be embedded in the then method of the returned object. I also know how to construct functions that return promises using the Q.js library.

My problem is that I do not know how to use the promises in practice. Whenever I try it becomes a mess and I end up having a bunch of then 's nested and most of the time nothing works. Not to mention that it is extremely common for me to fall into situations of the following type: I need certain data that is loaded from the server, this call returns a promise, but I need the data in a function that is executed soon after.

I can not really think the right way with asynchronous programming and promises. It is very natural to think that way "this happens first and then it has to happen", but it seems very strange to think "some time will happen and we do not know when it will end and something else must happen that depends on the first one". Could someone give some tips / references to get the hang of it?

    
asked by anonymous 24.05.2014 / 02:28

2 answers

39

In reality, if your program is well-structured the form of reasoning ("this happens first and then that has to happen") is not that different. Imagine a function of type:

function f() {
    var x = prompt("Digite x");
    var y = x * x;
    alert(y);
    console.log("pronto");
}

There are 4 instructions being executed, and each one has to happen after the previous one. Suppose you put each of them into a function (in this case it's overkill , but in general if a function is too large it makes sense to break it into smaller functions, right?):

function f() {
    var x, y;

    function a() { x = prompt("Digite x"); }
    function b() { y = X * x; }
    function c() { alert(y); }
    function d() { console.log("pronto"); }

    a();
    b();
    c();
    d();
}

In this case I took advantage of the fact that functions defined within other have access to your local variables . But I could also use the return value from one parameter to another:

function f() {
    function a() { return prompt("Digite x"); }
    function b(x) { return X * x; }
    function c(y) { alert(y); }
    function d() { console.log("pronto"); }

    d(c(b(a())));
}

In this case it becomes ugly (because the call is in reverse order), but the sequence in which the operations are performed is the same. Note that the function code remains well-organized and logical.

Now, how would it be if instead of reading the user input we made a request via Ajax to get it? What if in the end, instead of showing the user we made a second request to send to the server? With promises, taking the issue of data format the program remains the same!

function f() {
    function a() { return get('/le_valor/'); }
    function b(x) { return x * x; }
    function c(y) { return post('/resultado/', {valor:y}); }
    function d() { console.log("pronto"); }

    return a().then(b).then(c).then(d);
}

The fact that the computer is going to take a while between the execution of each of these functions does not matter - the fact is that they will be executed in the same logical sequence that you conceived.

In practice, of course you will not put each statement in a separate function, although in principle this is possible (in Q at least - since it mixes both synchronous and asynchronous functions well). Instead, all you need to do is see at what points an asynchronous operation is going to happen, and make sure your internal function finishes just this operation:

function f() {
    function a() { return get('/le_valor/'); }
    function b(x) { 
        var y = x * x;
        return post('/resultado/', {valor:y}); 
    }
    function d() { console.log("pronto"); }

    return a().then(b).then(d);
}

Notes:

  • If a function needs the result of two or more functions, just mix the example that uses closures with the example that returns the values. Example:

    function f() {
        var x, y;
    
        function a() { return get('/le_valor/'); }
        function b(data) { 
            x = data;
            y = x * x;
            return post('/resultado/', {valor:y}); 
        }
        function d() { console.log("O quadrado de " + x + " é " + y); }
    
        return a().then(b).then(d);
    }
    

    You could instead use a nested then , but in my opinion it gets harder to understand the code (as you've already noticed):

    function f() {
        function a() { return get('/le_valor/'); }
        function b(x) { 
            y = x * x;
            return post('/resultado/', {valor:y}).then(function() {
                console.log("O quadrado de " + x + " é " + y);
            }); 
        }
    
        return a().then(b);
    }
    
  • If another function needs to execute after f , no problem: since it also returns a promise, it can be used normally (since of course in the same style) :

    function g() {
        function a() { /* faz algo */ }
        function b() { /* faz algo - em seguida, tem que chamar f */ }
        function c() { /* faz algo - tem que ser executada depois de f */ }
        a().then(b).then(f).then(c);
    }
    

    or:

    function g() {
        function a() { /* faz algo */ }
        function b() { 
            /* faz algo */ 
            return f();
        }
        function c() { /* faz algo - tem que ser executada depois de f */ }
        a().then(b).then(c);
    }
    

    This second style is ideal if b needs to pass parameters to f . b must be synchronous, however. Otherwise, you can use the closures strategy as in the previous note.

  • In languages that support the concept of continuations , this kind of program structuring would not be necessary - you could have a normal function, in which an asynchronous call in the middle of it stopped everything the computer was doing, saved the execution stack and all its data, and when the call complete execution as if the interruption had never occurred. It would be the equivalent of a blocking call, but could be run in a single thread .

    Some people are interested in programming this way in JavaScript, even without language support ( example ). But in the end, it gets an ugly structuring, not unlike calls with callback . The use of promises is, in my opinion, a more elegant way of solving the problem of asynchronicity.

  • This article ( gives more examples of the practical use of Q. I wanted to cover more of it here, but the answer is already too extensive ... I'm stopping here, I hope you've clarified the reasoning behind the use of promises, and shown that it is not as difficult as it first seems.

24.05.2014 / 03:48
12

You can think of the promise as an implementation of Design Pattern Remote Proxy .

A promise is a method of solving a value (or not) asynchronously in a natural way. Promises are objects that represent the return value that a function can eventually provide.

Promises can also be objects representing a thrown exception.

Promises are extremely useful for dealing with remote objects where we can consider them as local copies (in a proxy) for our remote objects ( Remote Proxy Pattern ).

Traditionally, JavaScript uses Closure , or Callbacks to respond with meaningful data that is not available synchronously, such as AJAX requests - XHR after a page has been loaded.

Used Promise instead of relying on a Callback we can interact with the data as if it had already returned from the server, that is exactly where we have a Proxy for the object.

Callbacks have been used for a long time, but developers suffer when using this mechanism. Callbacks DO NOT provide consistency and your call is not guaranteed. In addition they "steal" the execution flow of the code when depending on other Callbacks. They usually make DEBUG incredibly difficult.

Instead of firing a function and "praying" for a callback run while executing asynchronous methods, promessas offers a different and much simpler abstraction : They return an object promise.

Let's look at an example: A user wants to send a message to a friend. We can write the Promises like this:

User.get(fromId)
  .then(function (user) {
    return  user.friends.find(toId);
  }, function (err) {
    // Não encontramos o usuário
  })
  .then(function (friend) {
    return  user.sendMessage(friend, message);
  }, function (err) {
    // Não consegui enviar a mensagem
  })
  .then(function (success) {
    // Mensagem enviada
   }, function (err) {
     // Ocorreu um erro inesperado
   });

This code is much more readable than a match using callbacks, and we can guarantee that the return will resolve to a single value, rather than having to deal with callback interfaces.

Error handling is also much more natural with Promises because we manipulate sucesso and erro in an analogous way.

That's it, I hope I've made it clear.

    
28.05.2014 / 15:09