npm-packages/meteor-promise/promise_server.js
var assert = require("assert");
var fiberPool = require("./fiber_pool.js").makePool();
exports.makeCompatible = function (Promise, Fiber) {
var es6PromiseThen = Promise.prototype.then;
if (typeof Fiber === "function") {
Promise.Fiber = Fiber;
}
if (es6PromiseThen.name === "meteorPromiseThen") {
return; // Already compatible.
}
function meteorPromiseThen(onResolved, onRejected) {
var Promise = this.constructor;
var Fiber = Promise.Fiber;
if (typeof Fiber === "function" &&
! this._meteorPromiseAlreadyWrapped) {
onResolved = wrapCallback(onResolved, Promise);
onRejected = wrapCallback(onRejected, Promise);
// Just in case we're wrapping a .then method defined by an older
// version of this library, make absolutely sure it doesn't attempt
// to rewrap the callbacks, and instead calls its own original
// es6PromiseThen function.
Promise.Fiber = null;
try {
return es6PromiseThen.call(this, onResolved, onRejected);
} finally {
Promise.Fiber = Fiber;
}
}
return es6PromiseThen.call(this, onResolved, onRejected);
}
// Replace Promise.prototype.then with a wrapper that ensures the
// onResolved and onRejected callbacks always run in a Fiber.
Object.defineProperty(Promise.prototype, "then", {
value: meteorPromiseThen,
enumerable: true,
// Don't let older versions of the meteor-promise library overwrite
// this version of Promise.prototype.then...
writable: false,
// ... unless they also call Object.defineProperty.
configurable: true
});
Promise.awaitAll = function (args) {
return awaitPromise(this.all(args));
};
Promise.await = function (arg) {
return awaitPromise(this.resolve(arg));
};
Promise.prototype.await = function () {
return awaitPromise(this);
};
// Yield the current Fiber until the given Promise has been fulfilled.
function awaitPromise(promise) {
var Promise = promise.constructor;
var Fiber = Promise.Fiber;
assert.strictEqual(
typeof Fiber, "function",
"Cannot await unless Promise.Fiber is defined"
);
var fiber = Fiber.current;
assert.ok(
fiber instanceof Fiber,
"Cannot await without a Fiber"
);
var run = fiber.run;
var throwInto = fiber.throwInto;
if (process.domain) {
run = process.domain.bind(run);
throwInto = process.domain.bind(throwInto);
}
// The overridden es6PromiseThen function is adequate here because these
// two callbacks do not need to run in a Fiber.
es6PromiseThen.call(promise, function (result) {
tryCatchNextTick(fiber, run, [result]);
}, function (error) {
tryCatchNextTick(fiber, throwInto, [error]);
});
return stackSafeYield(Fiber, awaitPromise);
}
function stackSafeYield(Fiber, caller) {
try {
return Fiber.yield();
} catch (thrown) {
if (thrown) {
var e = new Error;
Error.captureStackTrace(e, caller);
thrown.stack += e.stack.replace(/^.*?\n/, "\n => awaited here:\n");
}
throw thrown;
}
}
// Return a wrapper function that returns a Promise for the eventual
// result of the original function.
Promise.async = function (fn, allowReuseOfCurrentFiber) {
var Promise = this;
return function () {
return Promise.asyncApply(
fn, this, arguments,
allowReuseOfCurrentFiber
);
};
};
Promise.asyncApply = function (
fn, context, args, allowReuseOfCurrentFiber
) {
var Promise = this;
var Fiber = Promise.Fiber;
var fiber = Fiber && Fiber.current;
if (fiber && allowReuseOfCurrentFiber) {
return this.resolve(fn.apply(context, args));
}
return fiberPool.run({
callback: fn,
context: context,
args: args,
dynamics: cloneFiberOwnProperties(fiber)
}, Promise);
};
};
function wrapCallback(callback, Promise) {
if (! callback) {
return callback;
}
// Don't wrap callbacks that are flagged as not wanting to be called in a
// fiber.
if (callback._meteorPromiseAlreadyWrapped) {
return callback;
}
var dynamics = cloneFiberOwnProperties(Promise.Fiber.current);
var result = function (arg) {
var promise = fiberPool.run({
callback: callback,
args: [arg], // Avoid dealing with arguments objects.
dynamics: dynamics
}, Promise);
// Avoid wrapping the native resolver functions that will be attached
// to this promise per https://github.com/meteor/promise/issues/18.
promise._meteorPromiseAlreadyWrapped = true;
return promise;
};
// Flag this callback as not wanting to be called in a fiber because it is
// already creating a fiber.
result._meteorPromiseAlreadyWrapped = true;
return result;
}
function cloneFiberOwnProperties(fiber) {
if (fiber) {
var dynamics = {};
Object.keys(fiber).forEach(function (key) {
dynamics[key] = shallowClone(fiber[key]);
});
return dynamics;
}
}
function shallowClone(value) {
if (Array.isArray(value)) {
return value.slice(0);
}
if (!value || typeof value !== "object") {
return value;
}
if (value instanceof Map) {
return new Map(value);
}
if (value instanceof Set) {
return new Set(value);
}
const copy = Object.create(Object.getPrototypeOf(value));
const keys = Object.keys(value);
const keyCount = keys.length;
for (var i = 0; i < keyCount; ++i) {
const key = keys[i];
copy[key] = value[key];
}
return copy;
}
// Invoke method with args against object in a try-catch block,
// re-throwing any exceptions in the next tick of the event loop, so that
// they won't get captured/swallowed by the caller.
function tryCatchNextTick(object, method, args) {
try {
return method.apply(object, args);
} catch (error) {
process.nextTick(function () {
throw error;
});
}
}