How do you really learn how to use Javascript promises?

Asked

Viewed 2,271 times

46

For a while I’ve been trying to learn how to use promises and asynchronous programming in Javascript, but so far I haven’t had much success in it. I already understood that there are functions that return such promises and that then it is possible to fit a callback in the method then of the returned object. I also know how to build functions that return promises using the library Q.js.

My problem is that I don’t know how to use promises in practice. Every time I try I see a mess and end up having a lot 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’m actually not getting the right idea with asynchronous programming and promises. It is very natural to think that way "first this happens and then that has to happen", but it seems very strange to think "at some point this will happen and we don’t even know when it will end and something else that depends on the first one needs to happen". Could someone give some tips/references to get the hang of this?

  • 1

    I wrote an article about this https://epxx.co/artigos/nodejs3.html. Maybe help.

2 answers

42


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

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

There are four instructions being executed, and each of them has to happen after the previous one. Suppose you put each of them in a function (in this case it is Overkill, but in general if a function is too big 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 that case I took advantage of the fact that functions defined within other have access to their local variables. But I could also use the return value of one as a 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 gets ugly (because the call is in the reverse order), but the sequence in which the operations are executed 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 to the user we made a second request to send to the server? With promises, taking away the question 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 it will take a while between the execution of each of these functions no matter - the fact is that they will be executed in the same logical sequence that you designed.

In practice, it is clear that you will not put each instruction 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 will take place, and ensure that your internal function ends precisely in 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, simply mix the example it 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 then nestled, but in my opinion it becomes more difficult to understand the code (as you have 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 any other function needs to perform after f, no problem: like her also returns a promise, one can use it 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 needs to be synchronous, however. Otherwise, one can use the strategy of closures as in the previous note.

  • In languages that support the concept of continuations, this type of program structuring would not be necessary - one could have a normal function, in which an asynchronous call in the middle of it would stop everything the computer was doing, save the execution stack and all its data, and when the call was completed, the execution continued as if the interruption had never happened. It would be the equivalent of a block call (blocking call), but which could be executed in a single thread.

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

  • That article (in English) 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 will stop here, I hope you have clarified a little the reasoning behind the use of promises, and shown that it is not as difficult as it seems at first glance.

  • Wow! Even I do not get along very well with JS understood :o

  • Very good step-by-step, very didactic.

  • 1

    5 days looking for an explanation of this kind. The OS should have a button to make "Donations" for those who provide answers like these. Congratulations and thank you!

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) to our remote objects (Remote Proxy Pattern).

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

Used Promise instead of relying on one Callback we can interact with the data as if it had already come back from the server, that is exactly the case 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 code execution flow when depending on other Callbacks. They usually make DEBUG incredibly difficult.

Instead of firing a function and "pray" to get a callback run while running asynchronous methods, as promessas offer a different and much simpler abstraction: They return a promise object.

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 correspondent using callbacks and we can ensure that the return will resolve to a single value instead of having to deal with callback interfaces.

The error treatment is also much more natural with Promises, because we manipulate the sucesso and the erro in a similar way.

That’s it, I hope I made it clear.

  • 1

    I found very good the comparison and the citation of the advantages over the callback’s.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.