promisify.js 11 KB

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