1 | "use strict";
|
2 | module.exports = function(Promise, INTERNAL) {
|
3 | var THIS = {};
|
4 | var util = require("./util");
|
5 | var nodebackForPromise = require("./nodeback");
|
6 | var withAppended = util.withAppended;
|
7 | var maybeWrapAsError = util.maybeWrapAsError;
|
8 | var canEvaluate = util.canEvaluate;
|
9 | var TypeError = require("./errors").TypeError;
|
10 | var defaultSuffix = "Async";
|
11 | var defaultPromisified = {__isPromisified__: true};
|
12 | var noCopyProps = [
|
13 | "arity", "length",
|
14 | "name",
|
15 | "arguments",
|
16 | "caller",
|
17 | "callee",
|
18 | "prototype",
|
19 | "__isPromisified__"
|
20 | ];
|
21 | var noCopyPropsPattern = new RegExp("^(?:" + noCopyProps.join("|") + ")$");
|
22 |
|
23 | var defaultFilter = function(name) {
|
24 | return util.isIdentifier(name) &&
|
25 | name.charAt(0) !== "_" &&
|
26 | name !== "constructor";
|
27 | };
|
28 |
|
29 | function propsFilter(key) {
|
30 | return !noCopyPropsPattern.test(key);
|
31 | }
|
32 |
|
33 | function isPromisified(fn) {
|
34 | try {
|
35 | return fn.__isPromisified__ === true;
|
36 | }
|
37 | catch (e) {
|
38 | return false;
|
39 | }
|
40 | }
|
41 |
|
42 | function hasPromisified(obj, key, suffix) {
|
43 | var val = util.getDataPropertyOrDefault(obj, key + suffix,
|
44 | defaultPromisified);
|
45 | return val ? isPromisified(val) : false;
|
46 | }
|
47 | function checkValid(ret, suffix, suffixRegexp) {
|
48 | for (var i = 0; i < ret.length; i += 2) {
|
49 | var key = ret[i];
|
50 | if (suffixRegexp.test(key)) {
|
51 | var keyWithoutAsyncSuffix = key.replace(suffixRegexp, "");
|
52 | for (var j = 0; j < ret.length; j += 2) {
|
53 | if (ret[j] === keyWithoutAsyncSuffix) {
|
54 | throw new TypeError("Cannot promisify an API that has normal methods with '%s'-suffix\u000a\u000a See http://goo.gl/MqrFmX\u000a"
|
55 | .replace("%s", suffix));
|
56 | }
|
57 | }
|
58 | }
|
59 | }
|
60 | }
|
61 |
|
62 | function promisifiableMethods(obj, suffix, suffixRegexp, filter) {
|
63 | var keys = util.inheritedDataKeys(obj);
|
64 | var ret = [];
|
65 | for (var i = 0; i < keys.length; ++i) {
|
66 | var key = keys[i];
|
67 | var value = obj[key];
|
68 | var passesDefaultFilter = filter === defaultFilter
|
69 | ? true : defaultFilter(key, value, obj);
|
70 | if (typeof value === "function" &&
|
71 | !isPromisified(value) &&
|
72 | !hasPromisified(obj, key, suffix) &&
|
73 | filter(key, value, obj, passesDefaultFilter)) {
|
74 | ret.push(key, value);
|
75 | }
|
76 | }
|
77 | checkValid(ret, suffix, suffixRegexp);
|
78 | return ret;
|
79 | }
|
80 |
|
81 | var escapeIdentRegex = function(str) {
|
82 | return str.replace(/([$])/, "\\$");
|
83 | };
|
84 |
|
85 | var makeNodePromisifiedEval;
|
86 | if (!false) {
|
87 | var switchCaseArgumentOrder = function(likelyArgumentCount) {
|
88 | var ret = [likelyArgumentCount];
|
89 | var min = Math.max(0, likelyArgumentCount - 1 - 3);
|
90 | for(var i = likelyArgumentCount - 1; i >= min; --i) {
|
91 | ret.push(i);
|
92 | }
|
93 | for(var i = likelyArgumentCount + 1; i <= 3; ++i) {
|
94 | ret.push(i);
|
95 | }
|
96 | return ret;
|
97 | };
|
98 |
|
99 | var argumentSequence = function(argumentCount) {
|
100 | return util.filledRange(argumentCount, "_arg", "");
|
101 | };
|
102 |
|
103 | var parameterDeclaration = function(parameterCount) {
|
104 | return util.filledRange(
|
105 | Math.max(parameterCount, 3), "_arg", "");
|
106 | };
|
107 |
|
108 | var parameterCount = function(fn) {
|
109 | if (typeof fn.length === "number") {
|
110 | return Math.max(Math.min(fn.length, 1023 + 1), 0);
|
111 | }
|
112 | return 0;
|
113 | };
|
114 |
|
115 | makeNodePromisifiedEval =
|
116 | function(callback, receiver, originalName, fn, _, multiArgs) {
|
117 | var newParameterCount = Math.max(0, parameterCount(fn) - 1);
|
118 | var argumentOrder = switchCaseArgumentOrder(newParameterCount);
|
119 | var shouldProxyThis = typeof callback === "string" || receiver === THIS;
|
120 |
|
121 | function generateCallForArgumentCount(count) {
|
122 | var args = argumentSequence(count).join(", ");
|
123 | var comma = count > 0 ? ", " : "";
|
124 | var ret;
|
125 | if (shouldProxyThis) {
|
126 | ret = "ret = callback.call(this, {{args}}, nodeback); break;\n";
|
127 | } else {
|
128 | ret = receiver === undefined
|
129 | ? "ret = callback({{args}}, nodeback); break;\n"
|
130 | : "ret = callback.call(receiver, {{args}}, nodeback); break;\n";
|
131 | }
|
132 | return ret.replace("{{args}}", args).replace(", ", comma);
|
133 | }
|
134 |
|
135 | function generateArgumentSwitchCase() {
|
136 | var ret = "";
|
137 | for (var i = 0; i < argumentOrder.length; ++i) {
|
138 | ret += "case " + argumentOrder[i] +":" +
|
139 | generateCallForArgumentCount(argumentOrder[i]);
|
140 | }
|
141 |
|
142 | ret += " \n\
|
143 | default: \n\
|
144 | var args = new Array(len + 1); \n\
|
145 | var i = 0; \n\
|
146 | for (var i = 0; i < len; ++i) { \n\
|
147 | args[i] = arguments[i]; \n\
|
148 | } \n\
|
149 | args[i] = nodeback; \n\
|
150 | [CodeForCall] \n\
|
151 | break; \n\
|
152 | ".replace("[CodeForCall]", (shouldProxyThis
|
153 | ? "ret = callback.apply(this, args);\n"
|
154 | : "ret = callback.apply(receiver, args);\n"));
|
155 | return ret;
|
156 | }
|
157 |
|
158 | var getFunctionCode = typeof callback === "string"
|
159 | ? ("this != null ? this['"+callback+"'] : fn")
|
160 | : "fn";
|
161 | var body = "'use strict'; \n\
|
162 | var ret = function (Parameters) { \n\
|
163 | 'use strict'; \n\
|
164 | var len = arguments.length; \n\
|
165 | var promise = new Promise(INTERNAL); \n\
|
166 | promise._captureStackTrace(); \n\
|
167 | var nodeback = nodebackForPromise(promise, " + multiArgs + "); \n\
|
168 | var ret; \n\
|
169 | var callback = tryCatch([GetFunctionCode]); \n\
|
170 | switch(len) { \n\
|
171 | [CodeForSwitchCase] \n\
|
172 | } \n\
|
173 | if (ret === errorObj) { \n\
|
174 | promise._rejectCallback(maybeWrapAsError(ret.e), true, true);\n\
|
175 | } \n\
|
176 | if (!promise._isFateSealed()) promise._setAsyncGuaranteed(); \n\
|
177 | return promise; \n\
|
178 | }; \n\
|
179 | notEnumerableProp(ret, '__isPromisified__', true); \n\
|
180 | return ret; \n\
|
181 | ".replace("[CodeForSwitchCase]", generateArgumentSwitchCase())
|
182 | .replace("[GetFunctionCode]", getFunctionCode);
|
183 | body = body.replace("Parameters", parameterDeclaration(newParameterCount));
|
184 | return new Function("Promise",
|
185 | "fn",
|
186 | "receiver",
|
187 | "withAppended",
|
188 | "maybeWrapAsError",
|
189 | "nodebackForPromise",
|
190 | "tryCatch",
|
191 | "errorObj",
|
192 | "notEnumerableProp",
|
193 | "INTERNAL",
|
194 | body)(
|
195 | Promise,
|
196 | fn,
|
197 | receiver,
|
198 | withAppended,
|
199 | maybeWrapAsError,
|
200 | nodebackForPromise,
|
201 | util.tryCatch,
|
202 | util.errorObj,
|
203 | util.notEnumerableProp,
|
204 | INTERNAL);
|
205 | };
|
206 | }
|
207 |
|
208 | function makeNodePromisifiedClosure(callback, receiver, _, fn, __, multiArgs) {
|
209 | var defaultThis = (function() {return this;})();
|
210 | var method = callback;
|
211 | if (typeof method === "string") {
|
212 | callback = fn;
|
213 | }
|
214 | function promisified() {
|
215 | var _receiver = receiver;
|
216 | if (receiver === THIS) _receiver = this;
|
217 | var promise = new Promise(INTERNAL);
|
218 | promise._captureStackTrace();
|
219 | var cb = typeof method === "string" && this !== defaultThis
|
220 | ? this[method] : callback;
|
221 | var fn = nodebackForPromise(promise, multiArgs);
|
222 | try {
|
223 | cb.apply(_receiver, withAppended(arguments, fn));
|
224 | } catch(e) {
|
225 | promise._rejectCallback(maybeWrapAsError(e), true, true);
|
226 | }
|
227 | if (!promise._isFateSealed()) promise._setAsyncGuaranteed();
|
228 | return promise;
|
229 | }
|
230 | util.notEnumerableProp(promisified, "__isPromisified__", true);
|
231 | return promisified;
|
232 | }
|
233 |
|
234 | var makeNodePromisified = canEvaluate
|
235 | ? makeNodePromisifiedEval
|
236 | : makeNodePromisifiedClosure;
|
237 |
|
238 | function promisifyAll(obj, suffix, filter, promisifier, multiArgs) {
|
239 | var suffixRegexp = new RegExp(escapeIdentRegex(suffix) + "$");
|
240 | var methods =
|
241 | promisifiableMethods(obj, suffix, suffixRegexp, filter);
|
242 |
|
243 | for (var i = 0, len = methods.length; i < len; i+= 2) {
|
244 | var key = methods[i];
|
245 | var fn = methods[i+1];
|
246 | var promisifiedKey = key + suffix;
|
247 | if (promisifier === makeNodePromisified) {
|
248 | obj[promisifiedKey] =
|
249 | makeNodePromisified(key, THIS, key, fn, suffix, multiArgs);
|
250 | } else {
|
251 | var promisified = promisifier(fn, function() {
|
252 | return makeNodePromisified(key, THIS, key,
|
253 | fn, suffix, multiArgs);
|
254 | });
|
255 | util.notEnumerableProp(promisified, "__isPromisified__", true);
|
256 | obj[promisifiedKey] = promisified;
|
257 | }
|
258 | }
|
259 | util.toFastProperties(obj);
|
260 | return obj;
|
261 | }
|
262 |
|
263 | function promisify(callback, receiver, multiArgs) {
|
264 | return makeNodePromisified(callback, receiver, undefined,
|
265 | callback, null, multiArgs);
|
266 | }
|
267 |
|
268 | Promise.promisify = function (fn, options) {
|
269 | if (typeof fn !== "function") {
|
270 | throw new TypeError("expecting a function but got " + util.classString(fn));
|
271 | }
|
272 | if (isPromisified(fn)) {
|
273 | return fn;
|
274 | }
|
275 | options = Object(options);
|
276 | var receiver = options.context === undefined ? THIS : options.context;
|
277 | var multiArgs = !!options.multiArgs;
|
278 | var ret = promisify(fn, receiver, multiArgs);
|
279 | util.copyDescriptors(fn, ret, propsFilter);
|
280 | return ret;
|
281 | };
|
282 |
|
283 | Promise.promisifyAll = function (target, options) {
|
284 | if (typeof target !== "function" && typeof target !== "object") {
|
285 | throw new TypeError("the target of promisifyAll must be an object or a function\u000a\u000a See http://goo.gl/MqrFmX\u000a");
|
286 | }
|
287 | options = Object(options);
|
288 | var multiArgs = !!options.multiArgs;
|
289 | var suffix = options.suffix;
|
290 | if (typeof suffix !== "string") suffix = defaultSuffix;
|
291 | var filter = options.filter;
|
292 | if (typeof filter !== "function") filter = defaultFilter;
|
293 | var promisifier = options.promisifier;
|
294 | if (typeof promisifier !== "function") promisifier = makeNodePromisified;
|
295 |
|
296 | if (!util.isIdentifier(suffix)) {
|
297 | throw new RangeError("suffix must be a valid identifier\u000a\u000a See http://goo.gl/MqrFmX\u000a");
|
298 | }
|
299 |
|
300 | var keys = util.inheritedDataKeys(target);
|
301 | for (var i = 0; i < keys.length; ++i) {
|
302 | var value = target[keys[i]];
|
303 | if (keys[i] !== "constructor" &&
|
304 | util.isClass(value)) {
|
305 | promisifyAll(value.prototype, suffix, filter, promisifier,
|
306 | multiArgs);
|
307 | promisifyAll(value, suffix, filter, promisifier, multiArgs);
|
308 | }
|
309 | }
|
310 |
|
311 | return promisifyAll(target, suffix, filter, promisifier, multiArgs);
|
312 | };
|
313 | };
|
314 |
|