1 | "use strict";
|
2 |
|
3 | const Call = Function.prototype.call.bind(Function.prototype.call);
|
4 | const Apply = Function.prototype.call.bind(Function.prototype.apply);
|
5 |
|
6 | const ArrayReduce = Array.prototype.reduce;
|
7 |
|
8 | const I = require("immutable");
|
9 | const isIterable = I.Iterable.isIterable;
|
10 | const EmptyMap = I.Map();
|
11 |
|
12 | const program = require("@njudah/program");
|
13 | const AsynchronousRequest = require("./request");
|
14 | const AsynchronousResponse = AsynchronousRequest.AsynchronousResponse;
|
15 |
|
16 | const AsynchronousResponseEvent = I.Record({ responses: [] }, "AsynchronousResponseEvent");
|
17 |
|
18 | module.exports = function (aState, update, pull) {
|
19 | const asynchronousCache = Object.create(null);
|
20 | const push = program(aState, function (aState, anEvent) {
|
21 | const state = anEvent instanceof AsynchronousResponseEvent ? handleAsynchronousResponseEvent(aState, anEvent, asynchronousCache) : aState;
|
22 |
|
23 | return function exhaust(aState, anEvent) {
|
24 | const updatedState = update(aState, anEvent);
|
25 | const responseState = registerAsynchronousRequests(updatedState, pushAsynchronousResponse, asynchronousCache);
|
26 |
|
27 | if (aState === responseState)
|
28 | return aState;
|
29 |
|
30 | return exhaust(responseState, {});
|
31 | }(state, anEvent);
|
32 | }, function (aState) {
|
33 | pull(aState, getPendingAsynchronousFunctions(aState).size <= 0);
|
34 | });
|
35 | const pushAsynchronousResponse = getPushAsynchronousResponse(push);
|
36 |
|
37 | return push;
|
38 | };
|
39 |
|
40 | function registerAsynchronousRequests(aState, pushAsynchronousResponse, asynchronousCache) {
|
41 | const pendingAsynchronousFunctions = getPendingAsynchronousFunctions(aState);
|
42 |
|
43 | return pendingAsynchronousFunctions.reduce(function (aState, anAsynchronousFunction, aUUID) {
|
44 | if (!asynchronousCache[aUUID])
|
45 | {
|
46 | asynchronousCache[aUUID] = {
|
47 | cancel: anAsynchronousFunction.function(aResult => pushAsynchronousResponse(aUUID, AsynchronousResponse({ value: aResult })), aResult => pushAsynchronousResponse(aUUID, AsynchronousResponse({ isError: true, value: aResult })))
|
48 | };
|
49 |
|
50 | return aState;
|
51 | }
|
52 |
|
53 | if (!asynchronousCache[aUUID].response)
|
54 | return aState;
|
55 |
|
56 | const response = asynchronousCache[aUUID].response;
|
57 |
|
58 | return serviceAsynchronousRequests(getPendingAsynchronousFunctions, aState, aUUID, response);
|
59 | }, aState);
|
60 | }
|
61 |
|
62 | function getPushAsynchronousResponse(push, shouldCoallesce) {
|
63 | var responses = null;
|
64 |
|
65 | return function (aUUID, aResponse) {
|
66 | if (responses) return responses[aUUID] = aResponse;
|
67 |
|
68 | responses = { [aUUID]: aResponse };
|
69 |
|
70 | setTimeout(function () {
|
71 | const event = AsynchronousResponseEvent({ responses });
|
72 | responses = null;
|
73 | push(event);
|
74 | }, 5);
|
75 | };
|
76 | }
|
77 |
|
78 | function handleAsynchronousResponseEvent(anObject, anEvent, asynchronousCache) {
|
79 | const responses = anEvent.responses;
|
80 | const keys = Object.keys(responses);
|
81 |
|
82 | return Call(ArrayReduce, keys, function (anObject, aKey) {
|
83 | const response = responses[aKey];
|
84 | const hasExistingResponse = !!asynchronousCache[aKey].response;
|
85 |
|
86 | asynchronousCache[aKey].response = response;
|
87 |
|
88 | return serviceAsynchronousRequests(hasExistingResponse ? getAsynchronousFunctions : getPendingAsynchronousFunctions, anObject, aKey, responses[aKey]);
|
89 | }, anObject);
|
90 | }
|
91 |
|
92 | function serviceAsynchronousRequests(getAsynchronousFunctions, anObject, aUUID, aResponse) {
|
93 | if (!getAsynchronousFunctions(anObject).has(aUUID)) return anObject;
|
94 |
|
95 | if (anObject instanceof AsynchronousRequest) return anObject.function.UUID === aUUID ? anObject.set("response", aResponse) : anObject;
|
96 |
|
97 | return anObject.reduce(function (anObject, aValue, aKey) {
|
98 | const newValue = serviceAsynchronousRequests(getAsynchronousFunctions, aValue, aUUID, aResponse);
|
99 |
|
100 | if (newValue !== aValue) return anObject.set(aKey, newValue);
|
101 |
|
102 | return anObject;
|
103 | }, anObject);
|
104 | }
|
105 |
|
106 | function getAsynchronousFunctions(anObject) {
|
107 | return getCachedAsynchronousFunctions("__asynchronousFunctions", null, anObject);
|
108 | }
|
109 |
|
110 | function getPendingAsynchronousFunctions(anObject) {
|
111 | return getCachedAsynchronousFunctions("__prendingAsynchronous", function (anAsynchronousRequest) {
|
112 | return anAsynchronousRequest.response === null;
|
113 | }, anObject);
|
114 | }
|
115 |
|
116 | function getCachedAsynchronousFunctions(aCacheKey, aPredicate, anObject) {
|
117 | if (!anObject || anObject.__asynchronousIgnore) return EmptyMap;
|
118 |
|
119 | if (anObject[aCacheKey]) return anObject[aCacheKey];
|
120 |
|
121 | if (!isIterable(anObject)) return EmptyMap;
|
122 |
|
123 | return anObject[aCacheKey] = function () {
|
124 | if (anObject instanceof AsynchronousRequest) return !aPredicate || aPredicate(anObject) ? I.Map({ [anObject.function.UUID]: anObject.function }) : EmptyMap;
|
125 |
|
126 | return anObject.reduce(function (aMap, aValue) {
|
127 | const functions = getCachedAsynchronousFunctions(aCacheKey, aPredicate, aValue);
|
128 |
|
129 | if (functions === EmptyMap) return aMap;
|
130 |
|
131 | if (aMap === EmptyMap) return functions;
|
132 |
|
133 | return aMap.merge(functions);
|
134 | }, EmptyMap);
|
135 | }();
|
136 | } |
\ | No newline at end of file |