UNPKG

11.3 kBJavaScriptView Raw
1var Fiber = require('../fibers');
2var _ = require('lodash');
3var Config = require('./config');
4var FiberMgr = require('./fiberManager');
5var RunContext = require('./runContext');
6var Semaphore = require('./semaphore');
7var AsyncIterator = require('./asyncIterator');
8var defer = require('./defer');
9var await = require('../await/index');
10/** Function for creating a specific variant of the async function. */
11function makeAsyncFunc(config) {
12 // Validate the specified configuration
13 config.validate();
14 // Create an async function tailored to the given options.
15 var result = function async(bodyFunc) {
16 // Create a semaphore for limiting top-level concurrency, if specified in options.
17 var semaphore = config.maxConcurrency ? new Semaphore(config.maxConcurrency) : Semaphore.unlimited;
18 // Choose and run the appropriate function factory based on whether the result should be iterable.
19 var makeFunc = config.isIterable ? makeAsyncIterator : makeAsyncNonIterator;
20 var result = makeFunc(bodyFunc, config, semaphore);
21 // Ensure the suspendable function's arity matches that of the function it wraps.
22 var arity = bodyFunc.length;
23 if (config.acceptsCallback)
24 ++arity;
25 result = makeFuncWithArity(result, arity);
26 return result;
27 };
28 // Add the mod() function, and return the result.
29 result.mod = makeModFunc(config);
30 return result;
31}
32/** Function for creating iterable suspendable functions. */
33function makeAsyncIterator(bodyFunc, config, semaphore) {
34 // Return a function that returns an iterator.
35 return function iterable() {
36 // Capture the initial arguments used to start the iterator, as an array.
37 var startupArgs = new Array(arguments.length + 1); // Reserve 0th arg for the yield function.
38 for (var i = 0, len = arguments.length; i < len; ++i)
39 startupArgs[i + 1] = arguments[i];
40 // Create a yield() function tailored for this iterator.
41 var yield_ = function (expr) {
42 // Ensure this function is executing inside a fiber.
43 if (!Fiber.current) {
44 throw new Error('await functions, yield functions, and value-returning suspendable ' +
45 'functions may only be called from inside a suspendable function. ');
46 }
47 // Notify waiters of the next result, then suspend the iterator.
48 if (runContext.callback)
49 runContext.callback(null, { value: expr, done: false });
50 if (runContext.resolver)
51 runContext.resolver.resolve({ value: expr, done: false });
52 Fiber.yield();
53 };
54 // Insert the yield function as the first argument when starting the iterator.
55 startupArgs[0] = yield_;
56 // Create the iterator.
57 var runContext = new RunContext(bodyFunc, this, startupArgs);
58 var iterator = new AsyncIterator(runContext, semaphore, config.returnValue, config.acceptsCallback);
59 // Wrap the given bodyFunc to properly complete the iteration.
60 runContext.wrapped = function () {
61 var len = arguments.length, args = new Array(len);
62 for (var i = 0; i < len; ++i)
63 args[i] = arguments[i];
64 bodyFunc.apply(this, args);
65 iterator.destroy();
66 return { done: true };
67 };
68 // Return the iterator.
69 return iterator;
70 };
71}
72/** Function for creating non-iterable suspendable functions. */
73function makeAsyncNonIterator(bodyFunc, config, semaphore) {
74 // Return a function that executes fn in a fiber and returns a promise of fn's result.
75 return function nonIterable() {
76 // Get all the arguments passed in, as an array.
77 var argsAsArray = new Array(arguments.length);
78 for (var i = 0; i < argsAsArray.length; ++i)
79 argsAsArray[i] = arguments[i];
80 // Remove concurrency restrictions for nested calls, to avoid race conditions.
81 if (FiberMgr.isExecutingInFiber())
82 this._semaphore = Semaphore.unlimited;
83 // Configure the run context.
84 var runContext = new RunContext(bodyFunc, this, argsAsArray, function () { return semaphore.leave(); });
85 if (config.returnValue !== Config.NONE) {
86 var resolver = defer();
87 runContext.resolver = resolver;
88 }
89 if (config.acceptsCallback && argsAsArray.length && _.isFunction(argsAsArray[argsAsArray.length - 1])) {
90 var callback = argsAsArray.pop();
91 runContext.callback = callback;
92 }
93 // Execute bodyFunc to completion in a coroutine. For thunks, this is a lazy operation.
94 if (config.returnValue === Config.THUNK) {
95 var thunk = function (done) {
96 if (done)
97 resolver.promise.then(function (val) { return done(null, val); }, function (err) { return done(err); });
98 semaphore.enter(function () { return FiberMgr.create().run(runContext); });
99 };
100 }
101 else {
102 semaphore.enter(function () { return FiberMgr.create().run(runContext); });
103 }
104 // Return the appropriate value.
105 switch (config.returnValue) {
106 case Config.PROMISE: return resolver.promise;
107 case Config.THUNK: return thunk;
108 case Config.RESULT: return await(resolver.promise);
109 case Config.NONE: return;
110 }
111 };
112}
113/** Returns a function that directly proxies the given function, whilst reporting the given arity. */
114function makeFuncWithArity(fn, arity) {
115 // Need to handle each arity individually, but the body never changes.
116 switch (arity) {
117 case 0: return function f0() { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
118 r[i] = arguments[i]; return fn.apply(this, r); };
119 case 1: return function f1(a) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
120 r[i] = arguments[i]; return fn.apply(this, r); };
121 case 2: return function f2(a, b) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
122 r[i] = arguments[i]; return fn.apply(this, r); };
123 case 3: return function f3(a, b, c) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
124 r[i] = arguments[i]; return fn.apply(this, r); };
125 case 4: return function f4(a, b, c, d) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
126 r[i] = arguments[i]; return fn.apply(this, r); };
127 case 5: return function f5(a, b, c, d, e) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
128 r[i] = arguments[i]; return fn.apply(this, r); };
129 case 6: return function f6(a, b, c, d, e, f) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
130 r[i] = arguments[i]; return fn.apply(this, r); };
131 case 7: return function f7(a, b, c, d, e, f, g) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
132 r[i] = arguments[i]; return fn.apply(this, r); };
133 case 8: return function f8(a, b, c, d, e, f, g, h) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
134 r[i] = arguments[i]; return fn.apply(this, r); };
135 case 9: return function f9(a, b, c, d, e, f, g, h, _i) { var i, l = arguments.length, r = new Array(l); for (i = 0; i < l; ++i)
136 r[i] = arguments[i]; return fn.apply(this, r); };
137 default: return fn; // Bail out if arity is crazy high.
138 }
139}
140function makeModFunc(config) {
141 return function (options, maxConcurrency) {
142 if (_.isString(options)) {
143 // This way of specifying options is useful for TypeScript users, as they get better type information.
144 // JavaScript users can use this too, but providing an options hash is more useful in that case.
145 var rt, cb, it;
146 switch (options) {
147 case 'returns: promise, callback: false, iterable: false':
148 rt = 'promise';
149 cb = false;
150 it = false;
151 break;
152 case 'returns: thunk, callback: false, iterable: false':
153 rt = 'thunk';
154 cb = false;
155 it = false;
156 break;
157 case 'returns: result, callback: false, iterable: false':
158 rt = 'result';
159 cb = false;
160 it = false;
161 break;
162 case 'returns: promise, callback: true, iterable: false':
163 rt = 'promise';
164 cb = true;
165 it = false;
166 break;
167 case 'returns: thunk, callback: true, iterable: false':
168 rt = 'thunk';
169 cb = true;
170 it = false;
171 break;
172 case 'returns: result, callback: true, iterable: false':
173 rt = 'result';
174 cb = true;
175 it = false;
176 break;
177 case 'returns: none, callback: true, iterable: false':
178 rt = 'none';
179 cb = true;
180 it = false;
181 break;
182 case 'returns: promise, callback: false, iterable: true':
183 rt = 'promise';
184 cb = false;
185 it = true;
186 break;
187 case 'returns: thunk, callback: false, iterable: true':
188 rt = 'thunk';
189 cb = false;
190 it = true;
191 break;
192 case 'returns: result, callback: false, iterable: true':
193 rt = 'result';
194 cb = false;
195 it = true;
196 break;
197 case 'returns: promise, callback: true, iterable: true':
198 rt = 'promise';
199 cb = true;
200 it = true;
201 break;
202 case 'returns: thunk, callback: true, iterable: true':
203 rt = 'thunk';
204 cb = true;
205 it = true;
206 break;
207 case 'returns: result, callback: true, iterable: true':
208 rt = 'result';
209 cb = true;
210 it = true;
211 break;
212 case 'returns: none, callback: true, iterable: true':
213 rt = 'none';
214 cb = true;
215 it = true;
216 break;
217 }
218 options = { returnValue: rt, acceptsCallback: cb, isIterable: it, maxConcurrency: maxConcurrency };
219 }
220 var newConfig = new Config(_.defaults({}, options, config));
221 return makeAsyncFunc(newConfig);
222 };
223}
224module.exports = makeAsyncFunc;
225//# sourceMappingURL=makeAsyncFunc.js.map
\No newline at end of file