Again, async 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 );

A similar entry was met once in the comments, and that prompted an experiment - an exercise for the brain. Asynchronous operations on arrays in the form of a chain entry.

References, warnings and excuses
Pass and destroy , and if it falls off, download and thresh .

It is certain that another bicycle is presented here, and someone will consider the fact of the appearance of this code as ignorance. But as mentioned, this experiment is an attempt by the next encoder to "saddle" javascript asynchronous single-threaded. Whoever looks into the code will be discouraged by its quality, and the names of functions and variables can confuse the reader in trying to make sense. But there is a hope that this code will be useful to someone and will not be in vain.

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

function responseNotEmpty( r, v ) {
  if( /* что-то c v*/ ) r( true );
  else r( false );
}

The first argument in the handler is the service procedure - the receiver of the result, the second argument is an 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 ().

It was thought to turn to modern approaches of Promises and Asynchronous expectations, but they were discarded for inexplicable reasons. The result is a code consisting of potential leaks in half the functions. And its structure is a monstrous embodiment of callback-hell - 5 steps per hundred lines.

Two ways are thought out


At first, I wanted to create a large object with internal control of the state and execution of the entire chain of asynchronous handlers. That was represented in the form of a multidimensional array, a whole map with the results and flags, and microfunctions, and flags, and counters, and flags. This was good for one 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 links phantom for physical memory, you must try this in your code so that you don’t touch these links. Plus, the implementation of parallel branches of methods makes the map grow in several dimensions almost uncontrollably.

var a1 = arrayThatLeaks( [/**/] );
a1.leakMap(/**/).leakFilter(/**/).leakMap(/**/); // ветка 1
a1.leakFilter(/**/).leakReduce(/**/); // ветка 2

The second and current option involves creating a separate set of control data for each call to the array method. Here the difficulties mentioned collapse into a variable counter (almost). And for a bunch of methods, the takeforward () exchange procedure, the whole “magic” of local asynchronism, has been introduced into the chain. This procedure inside the closure of one method gets the internal procedures from the closure of the next method. It turned out what exactly to transmit during the writing process, it turned out that the procedure for starting the handler and the synchronizing procedure for controlling the counters were enough.

In detail


The first thing, as expected, was to separate the general auxiliary procedures from the specifics of different methods into the chain () function. Here the handlers and receivers from the methods wrap around, and the binding to the previous method occurs through the giveback () argument, which is usually the previous takeforward () procedure. Out of chain () comes the resulting array, extended by leak methods. The array expands in the expa () function, where each method is the result of the chain () operation. At the same time, to create a method, the synchronizer, the receiver and the preprocessor of the method are transferred to chain (), which turn around and get a few links from the chain () closure.

Such a scheme works for the map () and filter () methods that are simple in meaning. But to reduce (), you need to create external storages of intermediate values, which generates an anonym over chain (). Perhaps in the future, when there will be more methods, chain () will become more complicated to better describe methods with tightly coupled values, such as sorting.

Procedures that determine the logic of the method:


The method synchronizer is launched from the internal counter control procedure and receives the counters of its and previous circuit () closures, the flag for completing the previous method, a link to start the handler of its method, a link to start the next method, and a link to the result array. For simple methods, only the completion of the method execution is calculated here. For complex ones, the order in which the handlers are started is additionally determined with the substitution of all necessary values; therefore, links to the launch come to the synchronizer. Each time one method is synchronized, all subsequent ones are synchronized - this allows you to work out the logic of methods in advance.

ReceiverThe result is inserted by an argument into an external handler (method argument) and gets the value from there. Each receiver in the wrapper is associated with a specific index, can send some value down the chain further and starts synchronization. Starting a chain is not necessary here because the value obtained is far from always necessary to continue processing. And you can insert any index, if only without repetition.

Preprocessorthe method arose only to implement the convolution logic, of course, it will be useful for some other methods. The essence of the preprocessor is in intercepting values ​​from the processing chain. For reduce (), this is important, since the handler requires two values, and one comes from the chain, and more importantly, the reduce () logic requires stopping the chain altogether. In addition, the preprocessor can start synchronization - but it’s more like a crutch so as not to sprinkle the launch link on the procedures.

Eventually


Thus, the chain of methods is expanded and compressed by hidden procedures to ensure controlled asynchronous execution (how else?), And the array values ​​are processed through it, somewhere they pass without waiting for the others, somewhere they wait, somewhere they disappear. Leaks here are natural due to the chaining of closures, and any curve link will keep the whole chain. There is also a conceptual question of behavior in cases of emptying the array, throw an error or save to the results.

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

Also popular now: