The preface
Concurrency is a piece of logic that we deal with when we need to make multiple requests at the same time and wait for all of them to return. When we need to make multiple requests at the same time and need to handle the first request logic that returns the fastest, it is a race state. I’m sure good readers have dealt with this at some point. However, how to deal with these problems in functional programming is rarely discussed. In this article, we will briefly explain how to deal with these two types of problems through functional programming and the interaction between functors:
Const createTaskByNumber = n => new Task((rej, res) => {console.log(n); const createTaskByNumber = n => new Task((rej, res) => {console.log(n); setTimeout(() => res(n), n * 1000); }); const [http1, http3, http5] = [1, 3, 5].map(n => createTaskByNumber(n));Copy the code
So we have requests that are waiting 1/3/5 seconds for a response. The Task here comes from data. Task, which I have mentioned in previous articles.
now
Before we get to that, let’s compare the way things were done in ancient times.
Const collect: any[] = []; [http1, http3, http5].forEach((http, index) => { http().then(res => { collect[index] = res; if (collect.length === 3) { const [x, y, z] = collect; console.log(x + y + z); }}); }); Const collect: any[] = []; [http1, http3, http5].forEach((http, index) => { http().then(res => { collect[index] = res; if (collect.length === 1) { console.log(res); }}); });Copy the code
The principle is simple: execute business logic by maintaining an array and determining whether the contents of the array are all requested, thus handling concurrency and race states. The downside, however, is that we need to maintain more variables and the resulting maintenance costs.
Fortunately, ES6 brings us the most precious gift of all, promise.With promise.all&promise.race. Dealing with such problems has become more handy.
All ([http1, http3, http5]). Then (res => {const [x, y, z] = res; console.log(x + y + z) }); // Handle promise.race ([http1, http3, http5]). Then (res => {console.log(res); });Copy the code
Well, after the introduction of the current common solution, also should look at the functional programming scheme, may be a little disappointed, if you want to use functional and functor to deal with the above two requirements, the need for operation and understanding costs are not low, but it doesn’t matter, I will carefully explain, carefully to the source level.
Task.prototype.ap
Let’s start with ap, the core function for functional programming to handle concurrency. Data. task comments the AP function as follows:
/** * Applys the successful * value of the 'Task[α, (β → γ)]' to the successful * value of the 'Task[α, * * @ the summary beta] ` @ Task [alpha, beta and gamma ()] = > Task (alpha, beta) - > Task [alpha and gamma] * /Copy the code
We can apply ap in one functor to the value of resolve in another. This might sound a little abstract, but let’s see what AP looks like in action.
const {error, log} = console; Const add = new Task((rej, res) => {res(x => x + 1); }); Const one = new Task((rej, res) => {res(2); }); add.ap(one).fork(error, log) // 3Copy the code
At first glance, it seems ordinary, not surprising, I thought so at the beginning. Don’t worry. Let’s do something more complicated. Try adding asynchrony to it.
const add = new Task(function (reject, resolve) { setTimeout(function () { resolve(function (x) { return x + 1; }); }, 1000); }); const one = new Task(function (reject, resolve) { setTimeout(function () { resolve(2); }, 2000); }); add.ap(one).fork(error, log); / / 3Copy the code
No matter how complicated the logic in add is, even if you have asynchronous and delayed scripts, if you resolve to a function at the end of the day as successful value, so does one, if you resolve to a value, The result of add.ap(one) is the same. It is like the romance of add, when ap (love ❤️) joins One, but eventually they meet each other after a long journey.
Take a look at the magic of the AP function, even if you love (AP) across the sea, also need to ap your magic
Task.prototype = function _ap(that) {forkThis = this.fork; // Save add var forkThat = that.fork; // Save one var cleanupThis = this.cleanup; var cleanupThat = that.cleanup; function cleanupBoth(state) { cleanupThis(state[0]); cleanupThat(state[1]); } add.ap(one) return new task (function(reject, resolve) {// New task is forked. funcLoaded = false; Var val, valLoaded = false; Var rejected = false; var allState; ForkThis calls the add function (reject, reject). resolve) { // setTimeout(function () { // resolve(function (x) { // return x + 1; / /}); / /}, 1000); If thisState = forkThis(guardReject, reject guardReject) if thisState = forkThis(guardReject, reject guardReject) guardResolve(function(x) { funcLoaded = true; func = x; })); ForkThat (guardReject, guardResolve(function(x) {valLoaded = true; // forkThis (guardReject, guardResolve(function(x) {valLoaded = true; val = x; })); Resolve(setter) {return function(x) {// Reject (x) {// reject (x) {// reject (x) {// Reject (x) {// Reject (x) {// Reject (x) {// Reject (x) {// Reject (x) {// Reject (x) (rejected) { return; } // This is the key, the setter gets the final resolve value in the functor. // The setter of the add functor sets func funcLoaded. // The setter of the one functor sets val valLoaded setter(x); If (funcLoaded && valLoaded) {delayed(function(){delay (function(){delay (function(){delay (function(){ cleanupBoth(allState) }); Return resolve(func(val)); return resolve(func(val)); } else { return x; } } } function guardReject(x) { if (! rejected) { rejected = true; return reject(x); } } return allState = [thisState, thatState]; }, cleanupBoth); };Copy the code
Now that we’ve seen the magic of AP how can we use AP to handle concurrency? It’s not that far from the truth. Suppose the logic in ADD has no asynchrony, and the logic in one has asynchrony. Let’s add a few more one two three functors and append the add to multiple asynchrony functors at the same time. Let’s try it.
// Introduce task.of const of = task.of; const add = (x,y,z) => x + y + z; Add = task.of (curry(add)) const addTask = task.of (curry(add)); Const [http1, http3, http5] = [1, 3, 5]. Map (n => createTaskByNumber(n)); // show time addTask.ap(http1).ap(http3).ap(http5).fork(error, log); / / 9Copy the code
Each ap will return a new task, and the new task will execute a new func(val) internally. Each func(val) will return a new function, which will be handed to the NEXT AP’s func, thus realizing the concurrent processing logic.
But this isn’t elegant or useful, and requires an API like Promise.all to make concurrency a little more “fool transparent.” Start encapsulating an ALL handle
const all = curry((arr: any[], f) => { return new Task((rej, res) => { const task = arr.reduce((a, b) => { return a.ap(b); }, of(curry(f))); task.fork(rej, res); }); }); // Const handleAllHttp = curry((HTTPS, f) => compose(fork(error, log), all(HTTPS), () => f)); const handleHttp135 = handleAllHttp([http1, http3, http5]); Const addHttp135 = handleHttp135((x, y, z) => x + y + z); const minusHttp135 = handleHttp135((x, y, z) => x - y - z); const multiplyHttp135 = handleHttp135((x, y, z) => x * y * z); addHttp135(); // 9 minusHttp135(); // -7 multiplyHttp135(); / / 15Copy the code
conclusion
Functional programming is so charming, even today, still fascinated by the romance of AP functions ~