You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
315 lines
12 KiB
JavaScript
315 lines
12 KiB
JavaScript
6 years ago
|
"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);
|
||
|
};
|
||
|
};
|
||
|
|