• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"use strict";
2module.exports = function (Promise, apiRejection, tryConvertToPromise,
3    createContext, INTERNAL, debug) {
4    var util = require("./util");
5    var TypeError = require("./errors").TypeError;
6    var inherits = require("./util").inherits;
7    var errorObj = util.errorObj;
8    var tryCatch = util.tryCatch;
9    var NULL = {};
10
11    function thrower(e) {
12        setTimeout(function(){throw e;}, 0);
13    }
14
15    function castPreservingDisposable(thenable) {
16        var maybePromise = tryConvertToPromise(thenable);
17        if (maybePromise !== thenable &&
18            typeof thenable._isDisposable === "function" &&
19            typeof thenable._getDisposer === "function" &&
20            thenable._isDisposable()) {
21            maybePromise._setDisposable(thenable._getDisposer());
22        }
23        return maybePromise;
24    }
25    function dispose(resources, inspection) {
26        var i = 0;
27        var len = resources.length;
28        var ret = new Promise(INTERNAL);
29        function iterator() {
30            if (i >= len) return ret._fulfill();
31            var maybePromise = castPreservingDisposable(resources[i++]);
32            if (maybePromise instanceof Promise &&
33                maybePromise._isDisposable()) {
34                try {
35                    maybePromise = tryConvertToPromise(
36                        maybePromise._getDisposer().tryDispose(inspection),
37                        resources.promise);
38                } catch (e) {
39                    return thrower(e);
40                }
41                if (maybePromise instanceof Promise) {
42                    return maybePromise._then(iterator, thrower,
43                                              null, null, null);
44                }
45            }
46            iterator();
47        }
48        iterator();
49        return ret;
50    }
51
52    function Disposer(data, promise, context) {
53        this._data = data;
54        this._promise = promise;
55        this._context = context;
56    }
57
58    Disposer.prototype.data = function () {
59        return this._data;
60    };
61
62    Disposer.prototype.promise = function () {
63        return this._promise;
64    };
65
66    Disposer.prototype.resource = function () {
67        if (this.promise().isFulfilled()) {
68            return this.promise().value();
69        }
70        return NULL;
71    };
72
73    Disposer.prototype.tryDispose = function(inspection) {
74        var resource = this.resource();
75        var context = this._context;
76        if (context !== undefined) context._pushContext();
77        var ret = resource !== NULL
78            ? this.doDispose(resource, inspection) : null;
79        if (context !== undefined) context._popContext();
80        this._promise._unsetDisposable();
81        this._data = null;
82        return ret;
83    };
84
85    Disposer.isDisposer = function (d) {
86        return (d != null &&
87                typeof d.resource === "function" &&
88                typeof d.tryDispose === "function");
89    };
90
91    function FunctionDisposer(fn, promise, context) {
92        this.constructor$(fn, promise, context);
93    }
94    inherits(FunctionDisposer, Disposer);
95
96    FunctionDisposer.prototype.doDispose = function (resource, inspection) {
97        var fn = this.data();
98        return fn.call(resource, resource, inspection);
99    };
100
101    function maybeUnwrapDisposer(value) {
102        if (Disposer.isDisposer(value)) {
103            this.resources[this.index]._setDisposable(value);
104            return value.promise();
105        }
106        return value;
107    }
108
109    function ResourceList(length) {
110        this.length = length;
111        this.promise = null;
112        this[length-1] = null;
113    }
114
115    ResourceList.prototype._resultCancelled = function() {
116        var len = this.length;
117        for (var i = 0; i < len; ++i) {
118            var item = this[i];
119            if (item instanceof Promise) {
120                item.cancel();
121            }
122        }
123    };
124
125    Promise.using = function () {
126        var len = arguments.length;
127        if (len < 2) return apiRejection(
128                        "you must pass at least 2 arguments to Promise.using");
129        var fn = arguments[len - 1];
130        if (typeof fn !== "function") {
131            return apiRejection("expecting a function but got " + util.classString(fn));
132        }
133        var input;
134        var spreadArgs = true;
135        if (len === 2 && Array.isArray(arguments[0])) {
136            input = arguments[0];
137            len = input.length;
138            spreadArgs = false;
139        } else {
140            input = arguments;
141            len--;
142        }
143        var resources = new ResourceList(len);
144        for (var i = 0; i < len; ++i) {
145            var resource = input[i];
146            if (Disposer.isDisposer(resource)) {
147                var disposer = resource;
148                resource = resource.promise();
149                resource._setDisposable(disposer);
150            } else {
151                var maybePromise = tryConvertToPromise(resource);
152                if (maybePromise instanceof Promise) {
153                    resource =
154                        maybePromise._then(maybeUnwrapDisposer, null, null, {
155                            resources: resources,
156                            index: i
157                    }, undefined);
158                }
159            }
160            resources[i] = resource;
161        }
162
163        var reflectedResources = new Array(resources.length);
164        for (var i = 0; i < reflectedResources.length; ++i) {
165            reflectedResources[i] = Promise.resolve(resources[i]).reflect();
166        }
167
168        var resultPromise = Promise.all(reflectedResources)
169            .then(function(inspections) {
170                for (var i = 0; i < inspections.length; ++i) {
171                    var inspection = inspections[i];
172                    if (inspection.isRejected()) {
173                        errorObj.e = inspection.error();
174                        return errorObj;
175                    } else if (!inspection.isFulfilled()) {
176                        resultPromise.cancel();
177                        return;
178                    }
179                    inspections[i] = inspection.value();
180                }
181                promise._pushContext();
182
183                fn = tryCatch(fn);
184                var ret = spreadArgs
185                    ? fn.apply(undefined, inspections) : fn(inspections);
186                var promiseCreated = promise._popContext();
187                debug.checkForgottenReturns(
188                    ret, promiseCreated, "Promise.using", promise);
189                return ret;
190            });
191
192        var promise = resultPromise.lastly(function() {
193            var inspection = new Promise.PromiseInspection(resultPromise);
194            return dispose(resources, inspection);
195        });
196        resources.promise = promise;
197        promise._setOnCancel(resources);
198        return promise;
199    };
200
201    Promise.prototype._setDisposable = function (disposer) {
202        this._bitField = this._bitField | 131072;
203        this._disposer = disposer;
204    };
205
206    Promise.prototype._isDisposable = function () {
207        return (this._bitField & 131072) > 0;
208    };
209
210    Promise.prototype._getDisposer = function () {
211        return this._disposer;
212    };
213
214    Promise.prototype._unsetDisposable = function () {
215        this._bitField = this._bitField & (~131072);
216        this._disposer = undefined;
217    };
218
219    Promise.prototype.disposer = function (fn) {
220        if (typeof fn === "function") {
221            return new FunctionDisposer(fn, this, createContext());
222        }
223        throw new TypeError();
224    };
225
226};
227