Developing web applications is not much different from developing desktop applications. Some code will be responsible from managing the UI and some code will do the "heavy lifting" by implementing some business logic. You don't want to let your user hanging on slow response time and having a sluggish UI creating a bad user experience. However slow is your business logic, the UI shall respond quickly.
On the web, this problem is magnified by the fact that you business logic, most of the time, will be outsourced on a server somewhere hundreds or thousands kilometers away. Every HTTP call and, more generally, every function call that will take time shall be performed asynchronously, so that your UI is available to the user and is not being bog down by the rest of your application. You will be tempted to use threads to solve that problem. But if you ever worked with multi-threaded code, you should probably know how hard it is to get it right. A lot of subtle and easily reproducible issues will arise from multi-threaded code. And the cost of maintenance will dramatically increase.
But it seems that the Javascript authors got that right. Javascript was developed as a way to make web page more dynamic and enhance HTML so that you are not stuck with dumb static web pages which can sometimes be limited if you want a more dynamic web. Javascript is an event-driven language. The way it works is simple and clever. Any call that will be slow (HTTP call, request to a local storage, computing a SHA-1 hash, etc) will be made asynchronous. The browser might process it using multi-threaded code (or not, this is entirely an implementation detail) and will give back the CPU to you immediately and will generate an event when the answer is ready for the Javascript caller. This design is explained in detail on this Mozilla Developer Network page, which does a good job at distinguishing the the event queue and the function stack (which is the way most languages work).
In short, the event queue will be fed by events generated by the browser (user actions, end of processing, HTTP response) and the mono-threaded Javascript code consume those events one after the other in producer/consumer way. For example:
document.addEventListener('mousemove', () => console.log('mouse moved'));
const response = await fetch('http://example.com');
console.log(reponse);
There is no risk that multiple thread executes Javascript code at the same time here, even if the HTTP response comes back at the same moment a user move his mouse. The browser will push those events in the queue and only one of those events will be processed at the same time. This *greatly* simplifies your design. You will never have to worry about simultaneous execution of code and will never have to maintain complex system of mutexes and other semaphores to protect you from that rogue thread coming to get your resources.
Thanks to the event loop, complexity is being reduced by a design pattern, systematizing the management of asynchronous event and offering a simple and clean API to the user to manage that complexity.
I often find myself developing small proof-of-concepts using Javascript because of its ease of use, but sometimes I would like to be able to do the same in C++. This lead me to investigate the possibility to implement a simple event-loop in modern C++ with the following requirements:
Events can be user-defined (actually only one event is defined by default which is Timeout) and you do not require any overloading for that. For example, if you need a event representing a pressed key:
class KeydownEvent: public Event {
public:
KeydownEvent() {}
};
That's it. If you want to subscribe to such event:
EventLoop eventLoop;
std::thread mainThread(&EventLoop::run, &eventLoop);
eventLoop.add_event_listener<KeyDownEvent>([](KeyDownEvent& event) {
std::cout << "*" << event.key << "*" << std::endl;
});
With the advent of lambdas, writing event handler become as natural as in Javascript. Injecting event is pretty easy too:
eventLoop.inject_event(key);
The difficulties I had to face were mainly due to the type system. There are few possilities of reflection in C++, but there are still some useful tools like type_index. Variadic template arguments were also a little tricky to use, the syntax being far from obvious at first. But as an old-timer who learned C++98 back in the day, I was surprised at how C++ got easy to use with the latest new language features. The improvements made to the compilation error messages were a life saver.
Post-scriptum: In the process of implementing this event-loop, I discovered the "codereview" site of Stack Exchange, and was amazed by the quality of the reviews. Check it out!