📜 ⬆️ ⬇️

Again asynchronous on callbacks. In arrays

var nodes = arrayThatLeaks( linklist ) .leakMap( xhrLoad ) .leakFilter( responseNotEmpty ) .leakMap( insertInDocument ); //      Promise.resolve( arrayThatLeaks( linklist ) .leakMap( xhrLoad ) .leakFilter( responseNotEmpty ) .leakMap( makeDomNode ) ) .then( insertNodesInDocument ); 

Such a note was met once in the comments, and then she was inspired by the experiment - an exercise for the brain. Asynchronous array operations in the form of a chained record.

Links, warnings and excuses
Pass and destroy , and if you fall off download and thresh .

It is definitely that another bike is represented here, and someone will consider the fact of the appearance of this code for ignorance. But as mentioned, this experiment is an attempt of the next coder to “ride” single-threaded asynchrony javascript. Those who look into the code will be discouraged by its quality, and the names of functions and variables can confuse the reader in attempts to make sense. But there is hope that this code will be useful to someone and will not be wasted.

Naturally, method handlers must be written taking into account the asynchrony of what is happening. Here it is done in a simple way:

 function responseNotEmpty( r, v ) { if( /* - cv*/ ) r( true ); else r( false ); } 

The first argument in the handler is the service procedure - the result receiver, the second argument is the array element. The resulting array of nodes will be populated with values ​​as they pass through the chain of procedures. The order of the elements in the resulting array depends on which of the elements is processed earlier, that is, the values ​​of the initial array lose their index, even on map ().
')
The idea was to turn to the modern approaches of the Promises and Asynchronous expectations, but they were rejected for inexplicable reasons. As a result, a code was obtained consisting of potential leaks in half of the functions. And its structure is the monstrous embodiment of callback-hell - 5 steps per hundred lines.

Thought out two ways


At first, I wanted to create a large object with internal control of the state and execution of the entire chain of asynchronous processors. It was represented as a multidimensional array, a whole map with results and flags, and microfunctions, and flags, and counters, and flags. This was good for a single map () method, where the length of the array does not change. But the filtering method will make part of the map unnecessary. Even if the interpreter makes unnecessary references phantom for physical memory, it’s also necessary to try in the code so as not to touch these references. Plus, the implementation of parallel branches of the methods makes the map expand in several dimensions almost uncontrollably.

 var a1 = arrayThatLeaks( [/**/] ); a1.leakMap(/**/).leakFilter(/**/).leakMap(/**/); //  1 a1.leakFilter(/**/).leakReduce(/**/); //  2 

The second and current option is to create a separate set of control data for each call to the array method. Here, the mentioned difficulties collapse into a variable counter (almost). And for a bunch of methods, a takeforward () exchange procedure was introduced in the chain, all the “magic” of local asynchronism. This procedure inside the closure of one method gets the internal procedures from the closure of the next method. What specifically to transfer was found out in the course of writing, it turned out that it is enough procedure of start of the processor and synchronizing control procedure of counters.

In detail


First of all, as it should be, it was necessary to separate the general auxiliary procedures from the specifics of different methods into the chain () function. Here the handlers and receivers from the methods are wrapped, and the previous method is linked through the giveback () argument, which is usually the previous takeforward () procedure. From the chain () comes the resulting array, extended by "leak" methods. The array is expanded in the expa () function, where each method is the result of chain () operation. At the same time, to create a method in chain (), the synchronizer, receiver, and preprocessor of the method are passed, which turn around and get some links from the closure of chain ().

This scheme works for simple in terms of the map () and filter () methods. But for reduce () you need to create external storages of intermediate values, which creates an anonymous letter above chain (). Perhaps, in the future, when there are more methods, chain () will become more complicated for a better description of methods with tightly bound values, such as sorting.

Procedures defining the logic of the method:


The method synchronizer is launched from the internal counter control procedure and receives the counters of its own and previous chain () closure, the completion flag of the previous method, the reference to the launch of its own processor, the link to the next method, and the link to the results array. For simple methods, only the completeness of the method execution is calculated here. For complex ones, the order of launching handlers is additionally determined with the substitution of all necessary values, therefore, links to launch are sent to the synchronizer. Every time one method is synchronized, all subsequent ones are synchronized - this allows you to work out the logic of methods in advance.

The result receiver is inserted by the argument into the external handler (method argument) and gets the value from there. Each receiver in a wrapper is associated with a specific index, it can send some value further along the chain and starts synchronization. The start of the chain is optional here because the received value does not always need to continue processing. And you can insert any index, if only without repetitions.

The method preprocessor arose only for the implementation of convolution logic, of course, it will be useful for some other methods. The essence of the pre-processor in the interception of values ​​from the processing chain. For reduce (), this is important, because the handler requires two values, and one comes from the chain, and more importantly, the reduce () logic requires that the chain be completely stopped. In addition, the preprocessor can start synchronization - but this is more likely a crutch, so as not to disperse the launch references by procedures.

Eventually


Thus, the chain of methods is deployed and compacted by hidden procedures to provide controlled asynchronous execution (and how else?), And the values ​​of the array are processed by it, somewhere they pass without waiting for the others, somewhere they wait, they disappear somewhere. Leaks here are logical due to the chain of closures, and any curve link will hold the entire chain. There is also a conceptual question of behavior in cases of emptying an array, throwing an error or saving to the results.

UPD: Now you can combine with promises. To do this, there was a check in the data receiver wrapper and an external method .then (), which only pretends to be a promise.

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


All Articles