Back in the mid 2000s at IMVU, I went deep on reading about E, EROS, Polyphonic C#, and Comega, which lined directly up with the problems we were having with asynchronous programming on the client.
Thus, I can point directly to E and friends as leading us to make such a successful, _correct_, asynchronous task engine.
It's funny to think about now. Promises and coroutines and promise pipelining and async are now well-explored, but at the time there wasn't much prior art to draw from, and everyone was figuring it out as they went. Do you synchronously block on futures? Should .then calls run on the calling thread if data was already available? How does cancellation work?
Once you have answers to those questions, you reach the higher-level ones: how do you structure your program so that asynchronous components can communicate? How do you unit test these interactions? What abstractions avoid needing to unit test every possible sequence of events?
We did a pretty good job and I feel blessed that we stumbled upon the E papers, leading us to create abstractions that stood the test of time.
> Should .then calls run on the calling thread if data was already available?
This one hits close to home -- I remember being involved in a PR discussion for a JavaScript promises library where this was hotly (iirc?) debated. What conclusion did you reach? (I was on the side of consistency -- .then should not run synchronously even if it could.)
That's one where we got lucky. We made the wrong decision, but it didn't matter because the distinction was invisible when using the higher-level abstractions (like coroutines) and combinators.
My opinion now is that JavaScript does it right: `.then` should _always_ defer the callback. BUT, it does come at a real performance cost. And for code that is performance-sensitive and can handle the callback running immediately, the API should support a `.thenImmediate` or something that will run immediately if possible.
Yes, JS got it right. It is very often the case that you need to consider different edge cases for synchronous vs. asynchronous execution, and it's very hard to cover both cases in tests, leading to code that randomly breaks in production based on timing differences, which is pretty much the worst.
Thus, I can point directly to E and friends as leading us to make such a successful, _correct_, asynchronous task engine.
It's funny to think about now. Promises and coroutines and promise pipelining and async are now well-explored, but at the time there wasn't much prior art to draw from, and everyone was figuring it out as they went. Do you synchronously block on futures? Should .then calls run on the calling thread if data was already available? How does cancellation work?
Once you have answers to those questions, you reach the higher-level ones: how do you structure your program so that asynchronous components can communicate? How do you unit test these interactions? What abstractions avoid needing to unit test every possible sequence of events?
We did a pretty good job and I feel blessed that we stumbled upon the E papers, leading us to create abstractions that stood the test of time.
Thanks Mark!