2 module.exports = function(Promise, INTERNAL) {
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};
21 var noCopyPropsPattern = new RegExp("^(?:" + noCopyProps.join("|") + ")$");
23 var defaultFilter = function(name) {
24 return util.isIdentifier(name) &&
25 name.charAt(0) !== "_" &&
26 name !== "constructor";
29 function propsFilter(key) {
30 return !noCopyPropsPattern.test(key);
33 function isPromisified(fn) {
35 return fn.__isPromisified__ === true;
42 function hasPromisified(obj, key, suffix) {
43 var val = util.getDataPropertyOrDefault(obj, key + suffix,
45 return val ? isPromisified(val) : false;
47 function checkValid(ret, suffix, suffixRegexp) {
48 for (var i = 0; i < ret.length; i += 2) {
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));
62 function promisifiableMethods(obj, suffix, suffixRegexp, filter) {
63 var keys = util.inheritedDataKeys(obj);
65 for (var i = 0; i < keys.length; ++i) {
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)) {
77 checkValid(ret, suffix, suffixRegexp);
81 var escapeIdentRegex = function(str) {
82 return str.replace(/([$])/, "\\$");
85 var makeNodePromisifiedEval;
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) {
93 for(var i = likelyArgumentCount + 1; i <= 3; ++i) {
99 var argumentSequence = function(argumentCount) {
100 return util.filledRange(argumentCount, "_arg", "");
103 var parameterDeclaration = function(parameterCount) {
104 return util.filledRange(
105 Math.max(parameterCount, 3), "_arg", "");
108 var parameterCount = function(fn) {
109 if (typeof fn.length === "number") {
110 return Math.max(Math.min(fn.length, 1023 + 1), 0);
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;
121 function generateCallForArgumentCount(count) {
122 var args = argumentSequence(count).join(", ");
123 var comma = count > 0 ? ", " : "";
125 if (shouldProxyThis) {
126 ret = "ret = callback.call(this, {{args}}, nodeback); break;\n";
128 ret = receiver === undefined
129 ? "ret = callback({{args}}, nodeback); break;\n"
130 : "ret = callback.call(receiver, {{args}}, nodeback); break;\n";
132 return ret.replace("{{args}}", args).replace(", ", comma);
135 function generateArgumentSwitchCase() {
137 for (var i = 0; i < argumentOrder.length; ++i) {
138 ret += "case " + argumentOrder[i] +":" +
139 generateCallForArgumentCount(argumentOrder[i]);
144 var args = new Array(len + 1); \n\
146 for (var i = 0; i < len; ++i) { \n\
147 args[i] = arguments[i]; \n\
149 args[i] = nodeback; \n\
152 ".replace("[CodeForCall]", (shouldProxyThis
153 ? "ret = callback.apply(this, args);\n"
154 : "ret = callback.apply(receiver, args);\n"));
158 var getFunctionCode = typeof callback === "string"
159 ? ("this != null ? this['"+callback+"'] : fn")
161 var body = "'use strict'; \n\
162 var ret = function (Parameters) { \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\
169 var callback = tryCatch([GetFunctionCode]); \n\
171 [CodeForSwitchCase] \n\
173 if (ret === errorObj) { \n\
174 promise._rejectCallback(maybeWrapAsError(ret.e), true, true);\n\
176 if (!promise._isFateSealed()) promise._setAsyncGuaranteed(); \n\
179 notEnumerableProp(ret, '__isPromisified__', true); \n\
181 ".replace("[CodeForSwitchCase]", generateArgumentSwitchCase())
182 .replace("[GetFunctionCode]", getFunctionCode);
183 body = body.replace("Parameters", parameterDeclaration(newParameterCount));
184 return new Function("Promise",
189 "nodebackForPromise",
203 util.notEnumerableProp,
208 function makeNodePromisifiedClosure(callback, receiver, _, fn, __, multiArgs) {
209 var defaultThis = (function() {return this;})();
210 var method = callback;
211 if (typeof method === "string") {
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);
223 cb.apply(_receiver, withAppended(arguments, fn));
225 promise._rejectCallback(maybeWrapAsError(e), true, true);
227 if (!promise._isFateSealed()) promise._setAsyncGuaranteed();
230 util.notEnumerableProp(promisified, "__isPromisified__", true);
234 var makeNodePromisified = canEvaluate
235 ? makeNodePromisifiedEval
236 : makeNodePromisifiedClosure;
238 function promisifyAll(obj, suffix, filter, promisifier, multiArgs) {
239 var suffixRegexp = new RegExp(escapeIdentRegex(suffix) + "$");
241 promisifiableMethods(obj, suffix, suffixRegexp, filter);
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);
251 var promisified = promisifier(fn, function() {
252 return makeNodePromisified(key, THIS, key,
253 fn, suffix, multiArgs);
255 util.notEnumerableProp(promisified, "__isPromisified__", true);
256 obj[promisifiedKey] = promisified;
259 util.toFastProperties(obj);
263 function promisify(callback, receiver, multiArgs) {
264 return makeNodePromisified(callback, receiver, undefined,
265 callback, null, multiArgs);
268 Promise.promisify = function (fn, options) {
269 if (typeof fn !== "function") {
270 throw new TypeError("expecting a function but got " + util.classString(fn));
272 if (isPromisified(fn)) {
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);
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");
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;
296 if (!util.isIdentifier(suffix)) {
297 throw new RangeError("suffix must be a valid identifier\u000a\u000a See http://goo.gl/MqrFmX\u000a");
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,
307 promisifyAll(value, suffix, filter, promisifier, multiArgs);
311 return promisifyAll(target, suffix, filter, promisifier, multiArgs);