You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

promisify.js 12KB

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