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.
I wrote an article about this https://epxx.co/artigos/nodejs3.html. Maybe help.
– epx