Control flow of promises vs Rx-Observables
Coming from years of iOS development, I’ve come to be pretty fond of Rx-streams and observables as a way of taming state changes in a frontend-mobile environment. Javascript promises predates the development of Rx frameworks like ReactiveCocoa and RxSwift, so I was a little surprised to learn that Promises don’t offer the lazy evaluation that I’ve come to associate with reactive style programming.
For example:
const p = new Promise((resolve) => { // (1)
console.log("within promise");
setTimeout(() => {
resolve("hello world"); // (2)
console.log("promise resolved");
}, 3000);
});
console.log("test begins");
console.log(await p)
The order the resulting logs are:
within promise
test begins
promise resolved
hello world
I think several points were surprising at first:
- Immediately after the const t is created, the body of the promise closure is evaluated, which means that one cannot create multiple promises and ’then’ only one of the promises, since all of them will be evaluated. This also means that the long running task in the closure must use one of the built in IO or wait methods that can escape Javascript’s single-threadedness.
- The ‘resolve’ call only records the response of the function, and the control flow does not return to the ’then’ closure until the body of the promise is completed. It seems this way not because the promise was not resolved by the time ‘resolve’ is called, but because javascript has not finished executing this block of code, and has no way of ‘yielding’ the control flow back to the caller. Another modification makes this clearer:
const p = new Promise((resolve) => {
console.log("within promise");
setTimeout(() => {
resolve("hello world");
setTimeout(() => console.log("promise resolved"), 1);
}, 3000);
});
console.log("test begins");
console.log(await p)
Now the order of logs are:
within promise
test begins
hello world
promise resolved