blob: 65de531c11da24b485b59740807e6c3ea72e4a9e [file] [log] [blame]
"use strict";
module.exports = function (Promise, apiRejection, tryConvertToPromise,
createContext, INTERNAL, debug) {
var util = require("./util");
var TypeError = require("./errors").TypeError;
var inherits = require("./util").inherits;
var errorObj = util.errorObj;
var tryCatch = util.tryCatch;
var NULL = {};
function thrower(e) {
setTimeout(function(){throw e;}, 0);
}
function castPreservingDisposable(thenable) {
var maybePromise = tryConvertToPromise(thenable);
if (maybePromise !== thenable &&
typeof thenable._isDisposable === "function" &&
typeof thenable._getDisposer === "function" &&
thenable._isDisposable()) {
maybePromise._setDisposable(thenable._getDisposer());
}
return maybePromise;
}
function dispose(resources, inspection) {
var i = 0;
var len = resources.length;
var ret = new Promise(INTERNAL);
function iterator() {
if (i >= len) return ret._fulfill();
var maybePromise = castPreservingDisposable(resources[i++]);
if (maybePromise instanceof Promise &&
maybePromise._isDisposable()) {
try {
maybePromise = tryConvertToPromise(
maybePromise._getDisposer().tryDispose(inspection),
resources.promise);
} catch (e) {
return thrower(e);
}
if (maybePromise instanceof Promise) {
return maybePromise._then(iterator, thrower,
null, null, null);
}
}
iterator();
}
iterator();
return ret;
}
function Disposer(data, promise, context) {
this._data = data;
this._promise = promise;
this._context = context;
}
Disposer.prototype.data = function () {
return this._data;
};
Disposer.prototype.promise = function () {
return this._promise;
};
Disposer.prototype.resource = function () {
if (this.promise().isFulfilled()) {
return this.promise().value();
}
return NULL;
};
Disposer.prototype.tryDispose = function(inspection) {
var resource = this.resource();
var context = this._context;
if (context !== undefined) context._pushContext();
var ret = resource !== NULL
? this.doDispose(resource, inspection) : null;
if (context !== undefined) context._popContext();
this._promise._unsetDisposable();
this._data = null;
return ret;
};
Disposer.isDisposer = function (d) {
return (d != null &&
typeof d.resource === "function" &&
typeof d.tryDispose === "function");
};
function FunctionDisposer(fn, promise, context) {
this.constructor$(fn, promise, context);
}
inherits(FunctionDisposer, Disposer);
FunctionDisposer.prototype.doDispose = function (resource, inspection) {
var fn = this.data();
return fn.call(resource, resource, inspection);
};
function maybeUnwrapDisposer(value) {
if (Disposer.isDisposer(value)) {
this.resources[this.index]._setDisposable(value);
return value.promise();
}
return value;
}
function ResourceList(length) {
this.length = length;
this.promise = null;
this[length-1] = null;
}
ResourceList.prototype._resultCancelled = function() {
var len = this.length;
for (var i = 0; i < len; ++i) {
var item = this[i];
if (item instanceof Promise) {
item.cancel();
}
}
};
Promise.using = function () {
var len = arguments.length;
if (len < 2) return apiRejection(
"you must pass at least 2 arguments to Promise.using");
var fn = arguments[len - 1];
if (typeof fn !== "function") {
return apiRejection("expecting a function but got " + util.classString(fn));
}
var input;
var spreadArgs = true;
if (len === 2 && Array.isArray(arguments[0])) {
input = arguments[0];
len = input.length;
spreadArgs = false;
} else {
input = arguments;
len--;
}
var resources = new ResourceList(len);
for (var i = 0; i < len; ++i) {
var resource = input[i];
if (Disposer.isDisposer(resource)) {
var disposer = resource;
resource = resource.promise();
resource._setDisposable(disposer);
} else {
var maybePromise = tryConvertToPromise(resource);
if (maybePromise instanceof Promise) {
resource =
maybePromise._then(maybeUnwrapDisposer, null, null, {
resources: resources,
index: i
}, undefined);
}
}
resources[i] = resource;
}
var reflectedResources = new Array(resources.length);
for (var i = 0; i < reflectedResources.length; ++i) {
reflectedResources[i] = Promise.resolve(resources[i]).reflect();
}
var resultPromise = Promise.all(reflectedResources)
.then(function(inspections) {
for (var i = 0; i < inspections.length; ++i) {
var inspection = inspections[i];
if (inspection.isRejected()) {
errorObj.e = inspection.error();
return errorObj;
} else if (!inspection.isFulfilled()) {
resultPromise.cancel();
return;
}
inspections[i] = inspection.value();
}
promise._pushContext();
fn = tryCatch(fn);
var ret = spreadArgs
? fn.apply(undefined, inspections) : fn(inspections);
var promiseCreated = promise._popContext();
debug.checkForgottenReturns(
ret, promiseCreated, "Promise.using", promise);
return ret;
});
var promise = resultPromise.lastly(function() {
var inspection = new Promise.PromiseInspection(resultPromise);
return dispose(resources, inspection);
});
resources.promise = promise;
promise._setOnCancel(resources);
return promise;
};
Promise.prototype._setDisposable = function (disposer) {
this._bitField = this._bitField | 131072;
this._disposer = disposer;
};
Promise.prototype._isDisposable = function () {
return (this._bitField & 131072) > 0;
};
Promise.prototype._getDisposer = function () {
return this._disposer;
};
Promise.prototype._unsetDisposable = function () {
this._bitField = this._bitField & (~131072);
this._disposer = undefined;
};
Promise.prototype.disposer = function (fn) {
if (typeof fn === "function") {
return new FunctionDisposer(fn, this, createContext());
}
throw new TypeError();
};
};