promisify.js (12182B)
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