313 lines
12 KiB
JavaScript
313 lines
12 KiB
JavaScript
"use strict";
|
|
module.exports = function(Promise, INTERNAL) {
|
|
var THIS = {};
|
|
var util = require("./util.js");
|
|
var nodebackForPromise = require("./promise_resolver.js")
|
|
._nodebackForPromise;
|
|
var withAppended = util.withAppended;
|
|
var maybeWrapAsError = util.maybeWrapAsError;
|
|
var canEvaluate = util.canEvaluate;
|
|
var TypeError = require("./errors").TypeError;
|
|
var defaultSuffix = "Async";
|
|
var defaultFilter = function(name, func) {
|
|
return util.isIdentifier(name) &&
|
|
name.charAt(0) !== "_" &&
|
|
!util.isClass(func);
|
|
};
|
|
var defaultPromisified = {__isPromisified__: true};
|
|
|
|
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/iWrZbw\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 - 5);
|
|
for(var i = likelyArgumentCount - 1; i >= min; --i) {
|
|
ret.push(i);
|
|
}
|
|
for(var i = likelyArgumentCount + 1; i <= 5; ++i) {
|
|
ret.push(i);
|
|
}
|
|
return ret;
|
|
};
|
|
|
|
var argumentSequence = function(argumentCount) {
|
|
return util.filledRange(argumentCount, "arguments[", "]");
|
|
};
|
|
|
|
var parameterDeclaration = function(parameterCount) {
|
|
return util.filledRange(parameterCount, "_arg", "");
|
|
};
|
|
|
|
var parameterCount = function(fn) {
|
|
if (typeof fn.length === "number") {
|
|
return Math.max(Math.min(fn.length, 1023 + 1), 0);
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
var generatePropertyAccess = function(key) {
|
|
if (util.isIdentifier(key)) {
|
|
return "." + key;
|
|
}
|
|
else return "['" + key.replace(/(['\\])/g, "\\$1") + "']";
|
|
};
|
|
|
|
makeNodePromisifiedEval =
|
|
function(callback, receiver, originalName, fn, suffix) {
|
|
var newParameterCount = Math.max(0, parameterCount(fn) - 1);
|
|
var argumentOrder = switchCaseArgumentOrder(newParameterCount);
|
|
var callbackName =
|
|
(typeof originalName === "string" && util.isIdentifier(originalName)
|
|
? originalName + suffix
|
|
: "promisified");
|
|
|
|
function generateCallForArgumentCount(count) {
|
|
var args = argumentSequence(count).join(", ");
|
|
var comma = count > 0 ? ", " : "";
|
|
var ret;
|
|
if (typeof callback === "string") {
|
|
ret = " \n\
|
|
this.method({{args}}, fn); \n\
|
|
break; \n\
|
|
".replace(".method", generatePropertyAccess(callback));
|
|
} else if (receiver === THIS) {
|
|
ret = " \n\
|
|
callback.call(this, {{args}}, fn); \n\
|
|
break; \n\
|
|
";
|
|
} else if (receiver !== undefined) {
|
|
ret = " \n\
|
|
callback.call(receiver, {{args}}, fn); \n\
|
|
break; \n\
|
|
";
|
|
} else {
|
|
ret = " \n\
|
|
callback({{args}}, fn); \n\
|
|
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]);
|
|
}
|
|
var codeForCall;
|
|
if (typeof callback === "string") {
|
|
codeForCall = " \n\
|
|
this.property.apply(this, args); \n\
|
|
"
|
|
.replace(".property", generatePropertyAccess(callback));
|
|
} else if (receiver === THIS) {
|
|
codeForCall = " \n\
|
|
callback.apply(this, args); \n\
|
|
";
|
|
} else {
|
|
codeForCall = " \n\
|
|
callback.apply(receiver, args); \n\
|
|
";
|
|
}
|
|
|
|
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] = fn; \n\
|
|
[CodeForCall] \n\
|
|
break; \n\
|
|
".replace("[CodeForCall]", codeForCall);
|
|
return ret;
|
|
}
|
|
|
|
return new Function("Promise",
|
|
"callback",
|
|
"receiver",
|
|
"withAppended",
|
|
"maybeWrapAsError",
|
|
"nodebackForPromise",
|
|
"INTERNAL"," \n\
|
|
var ret = function (Parameters) { \n\
|
|
'use strict'; \n\
|
|
var len = arguments.length; \n\
|
|
var promise = new Promise(INTERNAL); \n\
|
|
promise._captureStackTrace(); \n\
|
|
promise._setIsSpreadable(); \n\
|
|
var fn = nodebackForPromise(promise); \n\
|
|
try { \n\
|
|
switch(len) { \n\
|
|
[CodeForSwitchCase] \n\
|
|
} \n\
|
|
} catch (e) { \n\
|
|
var wrapped = maybeWrapAsError(e); \n\
|
|
promise._attachExtraTrace(wrapped); \n\
|
|
promise._reject(wrapped); \n\
|
|
} \n\
|
|
return promise; \n\
|
|
}; \n\
|
|
ret.__isPromisified__ = true; \n\
|
|
return ret; \n\
|
|
"
|
|
.replace("FunctionName", callbackName)
|
|
.replace("Parameters", parameterDeclaration(newParameterCount))
|
|
.replace("[CodeForSwitchCase]", generateArgumentSwitchCase()))(
|
|
Promise,
|
|
callback,
|
|
receiver,
|
|
withAppended,
|
|
maybeWrapAsError,
|
|
nodebackForPromise,
|
|
INTERNAL
|
|
);
|
|
};
|
|
}
|
|
|
|
function makeNodePromisifiedClosure(callback, receiver) {
|
|
function promisified() {
|
|
var _receiver = receiver;
|
|
if (receiver === THIS) _receiver = this;
|
|
if (typeof callback === "string") {
|
|
callback = _receiver[callback];
|
|
}
|
|
var promise = new Promise(INTERNAL);
|
|
promise._captureStackTrace();
|
|
promise._setIsSpreadable();
|
|
var fn = nodebackForPromise(promise);
|
|
try {
|
|
callback.apply(_receiver, withAppended(arguments, fn));
|
|
} catch(e) {
|
|
var wrapped = maybeWrapAsError(e);
|
|
promise._attachExtraTrace(wrapped);
|
|
promise._reject(wrapped);
|
|
}
|
|
return promise;
|
|
}
|
|
promisified.__isPromisified__ = true;
|
|
return promisified;
|
|
}
|
|
|
|
var makeNodePromisified = canEvaluate
|
|
? makeNodePromisifiedEval
|
|
: makeNodePromisifiedClosure;
|
|
|
|
function promisifyAll(obj, suffix, filter, promisifier) {
|
|
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;
|
|
obj[promisifiedKey] = promisifier === makeNodePromisified
|
|
? makeNodePromisified(key, THIS, key, fn, suffix)
|
|
: promisifier(fn, function() {
|
|
return makeNodePromisified(key, THIS, key, fn, suffix);
|
|
});
|
|
}
|
|
util.toFastProperties(obj);
|
|
return obj;
|
|
}
|
|
|
|
function promisify(callback, receiver) {
|
|
return makeNodePromisified(callback, receiver, undefined, callback);
|
|
}
|
|
|
|
Promise.promisify = function (fn, receiver) {
|
|
if (typeof fn !== "function") {
|
|
throw new TypeError("fn must be a function\u000a\u000a See http://goo.gl/916lJJ\u000a");
|
|
}
|
|
if (isPromisified(fn)) {
|
|
return fn;
|
|
}
|
|
return promisify(fn, arguments.length < 2 ? THIS : receiver);
|
|
};
|
|
|
|
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/9ITlV0\u000a");
|
|
}
|
|
options = Object(options);
|
|
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/8FZo5V\u000a");
|
|
}
|
|
|
|
var keys = util.inheritedDataKeys(target, {includeHidden: true});
|
|
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);
|
|
promisifyAll(value, suffix, filter, promisifier);
|
|
}
|
|
}
|
|
|
|
return promisifyAll(target, suffix, filter, promisifier);
|
|
};
|
|
};
|
|
|