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.
314 lines
12 KiB
314 lines
12 KiB
"use strict"; |
|
module.exports = function(Promise, INTERNAL) { |
|
var THIS = {}; |
|
var util = require("./util"); |
|
var nodebackForPromise = require("./nodeback"); |
|
var withAppended = util.withAppended; |
|
var maybeWrapAsError = util.maybeWrapAsError; |
|
var canEvaluate = util.canEvaluate; |
|
var TypeError = require("./errors").TypeError; |
|
var defaultSuffix = "Async"; |
|
var defaultPromisified = {__isPromisified__: true}; |
|
var noCopyProps = [ |
|
"arity", "length", |
|
"name", |
|
"arguments", |
|
"caller", |
|
"callee", |
|
"prototype", |
|
"__isPromisified__" |
|
]; |
|
var noCopyPropsPattern = new RegExp("^(?:" + noCopyProps.join("|") + ")$"); |
|
|
|
var defaultFilter = function(name) { |
|
return util.isIdentifier(name) && |
|
name.charAt(0) !== "_" && |
|
name !== "constructor"; |
|
}; |
|
|
|
function propsFilter(key) { |
|
return !noCopyPropsPattern.test(key); |
|
} |
|
|
|
function isPromisified(fn) { |
|
try { |
|
return fn.__isPromisified__ === true; |
|
} |
|
catch (e) { |
|
return false; |
|
} |
|
} |
|
|
|
function hasPromisified(obj, key, suffix) { |
|
var val = util.getDataPropertyOrDefault(obj, key + suffix, |
|
defaultPromisified); |
|
return val ? isPromisified(val) : false; |
|
} |
|
function checkValid(ret, suffix, suffixRegexp) { |
|
for (var i = 0; i < ret.length; i += 2) { |
|
var key = ret[i]; |
|
if (suffixRegexp.test(key)) { |
|
var keyWithoutAsyncSuffix = key.replace(suffixRegexp, ""); |
|
for (var j = 0; j < ret.length; j += 2) { |
|
if (ret[j] === keyWithoutAsyncSuffix) { |
|
throw new TypeError("Cannot promisify an API that has normal methods with '%s'-suffix\u000a\u000a See http://goo.gl/MqrFmX\u000a" |
|
.replace("%s", suffix)); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
function promisifiableMethods(obj, suffix, suffixRegexp, filter) { |
|
var keys = util.inheritedDataKeys(obj); |
|
var ret = []; |
|
for (var i = 0; i < keys.length; ++i) { |
|
var key = keys[i]; |
|
var value = obj[key]; |
|
var passesDefaultFilter = filter === defaultFilter |
|
? true : defaultFilter(key, value, obj); |
|
if (typeof value === "function" && |
|
!isPromisified(value) && |
|
!hasPromisified(obj, key, suffix) && |
|
filter(key, value, obj, passesDefaultFilter)) { |
|
ret.push(key, value); |
|
} |
|
} |
|
checkValid(ret, suffix, suffixRegexp); |
|
return ret; |
|
} |
|
|
|
var escapeIdentRegex = function(str) { |
|
return str.replace(/([$])/, "\\$"); |
|
}; |
|
|
|
var makeNodePromisifiedEval; |
|
if (!false) { |
|
var switchCaseArgumentOrder = function(likelyArgumentCount) { |
|
var ret = [likelyArgumentCount]; |
|
var min = Math.max(0, likelyArgumentCount - 1 - 3); |
|
for(var i = likelyArgumentCount - 1; i >= min; --i) { |
|
ret.push(i); |
|
} |
|
for(var i = likelyArgumentCount + 1; i <= 3; ++i) { |
|
ret.push(i); |
|
} |
|
return ret; |
|
}; |
|
|
|
var argumentSequence = function(argumentCount) { |
|
return util.filledRange(argumentCount, "_arg", ""); |
|
}; |
|
|
|
var parameterDeclaration = function(parameterCount) { |
|
return util.filledRange( |
|
Math.max(parameterCount, 3), "_arg", ""); |
|
}; |
|
|
|
var parameterCount = function(fn) { |
|
if (typeof fn.length === "number") { |
|
return Math.max(Math.min(fn.length, 1023 + 1), 0); |
|
} |
|
return 0; |
|
}; |
|
|
|
makeNodePromisifiedEval = |
|
function(callback, receiver, originalName, fn, _, multiArgs) { |
|
var newParameterCount = Math.max(0, parameterCount(fn) - 1); |
|
var argumentOrder = switchCaseArgumentOrder(newParameterCount); |
|
var shouldProxyThis = typeof callback === "string" || receiver === THIS; |
|
|
|
function generateCallForArgumentCount(count) { |
|
var args = argumentSequence(count).join(", "); |
|
var comma = count > 0 ? ", " : ""; |
|
var ret; |
|
if (shouldProxyThis) { |
|
ret = "ret = callback.call(this, {{args}}, nodeback); break;\n"; |
|
} else { |
|
ret = receiver === undefined |
|
? "ret = callback({{args}}, nodeback); break;\n" |
|
: "ret = callback.call(receiver, {{args}}, nodeback); break;\n"; |
|
} |
|
return ret.replace("{{args}}", args).replace(", ", comma); |
|
} |
|
|
|
function generateArgumentSwitchCase() { |
|
var ret = ""; |
|
for (var i = 0; i < argumentOrder.length; ++i) { |
|
ret += "case " + argumentOrder[i] +":" + |
|
generateCallForArgumentCount(argumentOrder[i]); |
|
} |
|
|
|
ret += " \n\ |
|
default: \n\ |
|
var args = new Array(len + 1); \n\ |
|
var i = 0; \n\ |
|
for (var i = 0; i < len; ++i) { \n\ |
|
args[i] = arguments[i]; \n\ |
|
} \n\ |
|
args[i] = nodeback; \n\ |
|
[CodeForCall] \n\ |
|
break; \n\ |
|
".replace("[CodeForCall]", (shouldProxyThis |
|
? "ret = callback.apply(this, args);\n" |
|
: "ret = callback.apply(receiver, args);\n")); |
|
return ret; |
|
} |
|
|
|
var getFunctionCode = typeof callback === "string" |
|
? ("this != null ? this['"+callback+"'] : fn") |
|
: "fn"; |
|
var body = "'use strict'; \n\ |
|
var ret = function (Parameters) { \n\ |
|
'use strict'; \n\ |
|
var len = arguments.length; \n\ |
|
var promise = new Promise(INTERNAL); \n\ |
|
promise._captureStackTrace(); \n\ |
|
var nodeback = nodebackForPromise(promise, " + multiArgs + "); \n\ |
|
var ret; \n\ |
|
var callback = tryCatch([GetFunctionCode]); \n\ |
|
switch(len) { \n\ |
|
[CodeForSwitchCase] \n\ |
|
} \n\ |
|
if (ret === errorObj) { \n\ |
|
promise._rejectCallback(maybeWrapAsError(ret.e), true, true);\n\ |
|
} \n\ |
|
if (!promise._isFateSealed()) promise._setAsyncGuaranteed(); \n\ |
|
return promise; \n\ |
|
}; \n\ |
|
notEnumerableProp(ret, '__isPromisified__', true); \n\ |
|
return ret; \n\ |
|
".replace("[CodeForSwitchCase]", generateArgumentSwitchCase()) |
|
.replace("[GetFunctionCode]", getFunctionCode); |
|
body = body.replace("Parameters", parameterDeclaration(newParameterCount)); |
|
return new Function("Promise", |
|
"fn", |
|
"receiver", |
|
"withAppended", |
|
"maybeWrapAsError", |
|
"nodebackForPromise", |
|
"tryCatch", |
|
"errorObj", |
|
"notEnumerableProp", |
|
"INTERNAL", |
|
body)( |
|
Promise, |
|
fn, |
|
receiver, |
|
withAppended, |
|
maybeWrapAsError, |
|
nodebackForPromise, |
|
util.tryCatch, |
|
util.errorObj, |
|
util.notEnumerableProp, |
|
INTERNAL); |
|
}; |
|
} |
|
|
|
function makeNodePromisifiedClosure(callback, receiver, _, fn, __, multiArgs) { |
|
var defaultThis = (function() {return this;})(); |
|
var method = callback; |
|
if (typeof method === "string") { |
|
callback = fn; |
|
} |
|
function promisified() { |
|
var _receiver = receiver; |
|
if (receiver === THIS) _receiver = this; |
|
var promise = new Promise(INTERNAL); |
|
promise._captureStackTrace(); |
|
var cb = typeof method === "string" && this !== defaultThis |
|
? this[method] : callback; |
|
var fn = nodebackForPromise(promise, multiArgs); |
|
try { |
|
cb.apply(_receiver, withAppended(arguments, fn)); |
|
} catch(e) { |
|
promise._rejectCallback(maybeWrapAsError(e), true, true); |
|
} |
|
if (!promise._isFateSealed()) promise._setAsyncGuaranteed(); |
|
return promise; |
|
} |
|
util.notEnumerableProp(promisified, "__isPromisified__", true); |
|
return promisified; |
|
} |
|
|
|
var makeNodePromisified = canEvaluate |
|
? makeNodePromisifiedEval |
|
: makeNodePromisifiedClosure; |
|
|
|
function promisifyAll(obj, suffix, filter, promisifier, multiArgs) { |
|
var suffixRegexp = new RegExp(escapeIdentRegex(suffix) + "$"); |
|
var methods = |
|
promisifiableMethods(obj, suffix, suffixRegexp, filter); |
|
|
|
for (var i = 0, len = methods.length; i < len; i+= 2) { |
|
var key = methods[i]; |
|
var fn = methods[i+1]; |
|
var promisifiedKey = key + suffix; |
|
if (promisifier === makeNodePromisified) { |
|
obj[promisifiedKey] = |
|
makeNodePromisified(key, THIS, key, fn, suffix, multiArgs); |
|
} else { |
|
var promisified = promisifier(fn, function() { |
|
return makeNodePromisified(key, THIS, key, |
|
fn, suffix, multiArgs); |
|
}); |
|
util.notEnumerableProp(promisified, "__isPromisified__", true); |
|
obj[promisifiedKey] = promisified; |
|
} |
|
} |
|
util.toFastProperties(obj); |
|
return obj; |
|
} |
|
|
|
function promisify(callback, receiver, multiArgs) { |
|
return makeNodePromisified(callback, receiver, undefined, |
|
callback, null, multiArgs); |
|
} |
|
|
|
Promise.promisify = function (fn, options) { |
|
if (typeof fn !== "function") { |
|
throw new TypeError("expecting a function but got " + util.classString(fn)); |
|
} |
|
if (isPromisified(fn)) { |
|
return fn; |
|
} |
|
options = Object(options); |
|
var receiver = options.context === undefined ? THIS : options.context; |
|
var multiArgs = !!options.multiArgs; |
|
var ret = promisify(fn, receiver, multiArgs); |
|
util.copyDescriptors(fn, ret, propsFilter); |
|
return ret; |
|
}; |
|
|
|
Promise.promisifyAll = function (target, options) { |
|
if (typeof target !== "function" && typeof target !== "object") { |
|
throw new TypeError("the target of promisifyAll must be an object or a function\u000a\u000a See http://goo.gl/MqrFmX\u000a"); |
|
} |
|
options = Object(options); |
|
var multiArgs = !!options.multiArgs; |
|
var suffix = options.suffix; |
|
if (typeof suffix !== "string") suffix = defaultSuffix; |
|
var filter = options.filter; |
|
if (typeof filter !== "function") filter = defaultFilter; |
|
var promisifier = options.promisifier; |
|
if (typeof promisifier !== "function") promisifier = makeNodePromisified; |
|
|
|
if (!util.isIdentifier(suffix)) { |
|
throw new RangeError("suffix must be a valid identifier\u000a\u000a See http://goo.gl/MqrFmX\u000a"); |
|
} |
|
|
|
var keys = util.inheritedDataKeys(target); |
|
for (var i = 0; i < keys.length; ++i) { |
|
var value = target[keys[i]]; |
|
if (keys[i] !== "constructor" && |
|
util.isClass(value)) { |
|
promisifyAll(value.prototype, suffix, filter, promisifier, |
|
multiArgs); |
|
promisifyAll(value, suffix, filter, promisifier, multiArgs); |
|
} |
|
} |
|
|
|
return promisifyAll(target, suffix, filter, promisifier, multiArgs); |
|
}; |
|
}; |
|
|
|
|