📜 ⬆️ ⬇️

42 lines of code to exit the limb

You know how it happens: a large project is projected for a long time, it is written for a long time, sometimes it is exhausted and eventually gives up. A month goes by another “hot debugging”, and after that comes a reverent silence. From the customer can not hear anything. And not because he collapsed because of your work; his phone bills are not paid for him, and the Internet has long been disconnected, no) It just works for him normally.

But one day ... Right! Soap arrives, “ your program does not work ” ((C) bash), the phones warm up to red, and the lawyers nervously re-read what they have thrown into the “warranty service” section.

Exactly this situation was with us. We did a fairly weighty project, the essence of which could be described as follows (briefly, of course): there are different content (client base, marketing base, linkage base, etc., etc., etc.) and various ways of presenting it (widget, popup, modal etc .). In other words, from our side a platform was prepared (data access API, visualization, the whole ecosystem (although I don’t know what that means, but it sounds very cool)) so that the customer’s developers could write their own controllers data and just file them “put” in the specified place, then happily contemplate how a new widget appears with a list of current quotes for some tricky index.
')
And as I said, everything went well. Spent several "master" classes, all showed, all told, drank beer and spun. Already without us.

Until it all broke. That's right: "everything" and "broke." At some moments, the application just began to hang tight. So much so that the browser tab can not be closed. A little bit experienced web-developer will immediately say - you have a cycle somewhere wedged children. And it will be right that there.

But before moving on to the 42 lines of code, I’ll just remind you that when there is an application with a bunch of all sorts of goodies inside, then the best way to organize communication between them is internal events. And ours at the time of delivery of the project were under a hundred, and when the problems started, their number grew by another couple of dozen.

And, as you already guessed, the "wedge" is just the event controller. On the fingers: event A, causes event B, and event B - event C, and it, in turn, again causes event A. Ta-da-m, meet the cycle!

Our event handler was outrageously simple and huddled in a file on 44 lines of code. However, he did not know how to do a very relevant thing - to check if he was in the cycle.

I did not have to think much about drinking and found a solution rather quickly. Good or bad is all in your judgment. I will describe only the main idea.

The only way to check the “who” caused a chain of events (in our example, find A, B and C) is to check the stack. To get the stack, you just need to throw the error away.

The problem remains - how to “mark” the place where the event was triggered, because there should be something on the stack that would help to recognize the entire chain of previously running events? One of the solutions to this problem is the named wrapper functions, in the name of which all information about previous events is stored. Do not understand? I've written, re-read and also did not understand. Easier to see the code .

So now, if you do this (event A triggers B, B triggers C, and C triggers A again):

var safeevents = new SafeEvents(); safeevents.bind('A', function () { safeevents.trigger('B'); }); safeevents.bind('B', function () { safeevents.trigger('C'); }); safeevents.bind('C', function () { safeevents.trigger('A'); }); safeevents.trigger('A'); 

Then this time the application will not go to the limb, but will throw into the console the exception “Uncaught Error: Event [A] called itself. Full chain: A, B, C ". Profit. Now the developer does not need to go to three additional breaks to figure out what is actually the case - everything can be seen from the message in the console.

Asynchronous calls are a bit more complicated. For them, alas, you need to perform an additional action. But it is so tiny that it is unlikely to be a big problem.

  var safeevents = new SafeEvents(); safeevents.bind('A', function () { safeevents.trigger('B'); }); safeevents.bind('B', function () { safeevents.trigger('C'); }); safeevents.bind('C', function () { /* * Use method "safely" to wrap your async methods and create safe callback. */ setTimeout(safeevents.safely(function () { safeevents.trigger('A'); }), 10); }); safeevents.trigger('A'); 

Pay attention to the callback function in the timer. We add a "wrapper" to transfer data about previous events in asynchronous calls. And again in the console we will see: “Uncaught Error: Event [A] is called itself. Full chain: A, B, C ".

Of course, it’s not always necessary to brazenly throw an exception. It is much better to quietly tell where to write the data to the log or send a notification to the admin. For what you can put your handler on the case of "looping" and get all the necessary data.

  var safeevents = new SafeEvents(); safeevents.bind('A', function () { safeevents.trigger('B'); }); safeevents.bind('B', function () { safeevents.trigger('C'); }); safeevents.bind('C', function () { safeevents.trigger('A'); }); safeevents.bind(safeevents.onloop, function (e, chain, last_event, stack) { console.log('Error message: ' + e); console.log('Full chain of events: ' + chain.join(', ')); console.log('Last event (generated loop): ' + last_event); console.log('Error stack: ' + stack); }); safeevents.trigger('A'); 

Now our application is not interrupted at all, but the cycle will be successfully prevented.

In general, by rewriting our simplest event handler and receiving an additional 42 lines of code, we solved a very rare but rather dirty problem with looping events. Who knows (I do not know), maybe it will be useful to you. It 's all here .

Happiness, goodness and electricity in your home.

Source: https://habr.com/ru/post/313422/


All Articles