• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"use strict";
2module.exports = function(Promise, INTERNAL) {
3var THIS = {};
4var util = require("./util");
5var nodebackForPromise = require("./nodeback");
6var withAppended = util.withAppended;
7var maybeWrapAsError = util.maybeWrapAsError;
8var canEvaluate = util.canEvaluate;
9var TypeError = require("./errors").TypeError;
10var defaultSuffix = "Async";
11var defaultPromisified = {__isPromisified__: true};
12var noCopyProps = [
13    "arity",    "length",
14    "name",
15    "arguments",
16    "caller",
17    "callee",
18    "prototype",
19    "__isPromisified__"
20];
21var noCopyPropsPattern = new RegExp("^(?:" + noCopyProps.join("|") + ")$");
22
23var defaultFilter = function(name) {
24    return util.isIdentifier(name) &&
25        name.charAt(0) !== "_" &&
26        name !== "constructor";
27};
28
29function propsFilter(key) {
30    return !noCopyPropsPattern.test(key);
31}
32
33function isPromisified(fn) {
34    try {
35        return fn.__isPromisified__ === true;
36    }
37    catch (e) {
38        return false;
39    }
40}
41
42function hasPromisified(obj, key, suffix) {
43    var val = util.getDataPropertyOrDefault(obj, key + suffix,
44                                            defaultPromisified);
45    return val ? isPromisified(val) : false;
46}
47function 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
62function 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
81var escapeIdentRegex = function(str) {
82    return str.replace(/([$])/, "\\$");
83};
84
85var makeNodePromisifiedEval;
86if (!false) {
87var 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
99var argumentSequence = function(argumentCount) {
100    return util.filledRange(argumentCount, "_arg", "");
101};
102
103var parameterDeclaration = function(parameterCount) {
104    return util.filledRange(
105        Math.max(parameterCount, 3), "_arg", "");
106};
107
108var 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
115makeNodePromisifiedEval =
116function(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
208function 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
234var makeNodePromisified = canEvaluate
235    ? makeNodePromisifiedEval
236    : makeNodePromisifiedClosure;
237
238function 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
263function promisify(callback, receiver, multiArgs) {
264    return makeNodePromisified(callback, receiver, undefined,
265                                callback, null, multiArgs);
266}
267
268Promise.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
283Promise.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
315