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.