@esfx/disposable
Version:
A low-level API for defining explicit resource management.
293 lines (283 loc) • 12.8 kB
JavaScript
/*!
Copyright 2019 Ron Buckton
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
THIRD PARTY LICENSE NOTICE:
CreateScope is derived from https://github.com/mhofman/disposator/ which
is licensed under the Apache 2.0 License:
Copyright 2021 Mathieu Hofman
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* @internal */
import * as asyncDisposable_js_1 from "../asyncDisposable.mjs";
/* @internal */
import * as disposable_js_1 from "../disposable.mjs";
export function GetMethod(V, P) {
// ECMA262 7.3.11 GetMethod ( _V_, _P_ )
const func = V[P];
if (func === null || func === undefined)
return undefined;
if (!(typeof func === "function"))
throw new TypeError(`Property ${typeof P === "symbol" ? P.toString() : JSON.stringify(P)} is not a function.`);
return func;
}
/* @internal */
export var Call =
// ECMA262 7.3.14 Call ( _F_, _V_ [ , _argumentsList_ ] )
Function.prototype.call.bind(Function.prototype.call);
/* @internal */
export function SpeciesConstructor(O, defaultConstructor) {
// ECMA262 7.3.23 SpeciesConstructor ( _O_, _defaultConstructor_ )
const C = O.constructor;
if (C === undefined)
return defaultConstructor;
if (!(typeof C === "object" && C !== null || typeof C === "function"))
throw new TypeError("Object expected");
const S = O[Symbol.species];
if (S === null || S === undefined)
return defaultConstructor;
if (typeof S === "function")
return S;
throw new TypeError("constructor not found");
}
/* @internal */
export function AddDisposableResource(disposableResourceStack, V, hint, method) {
// 3.1.2 AddDisposableResource ( _disposable_, _V_, _hint_ [ , _method_ ] )
let resource;
// 1. If _method_ is not present then,
if (!method) {
// a. If _V_ is *null* or *undefined*, return NormalCompletion(~empty~).
if (V === null || V === undefined)
return;
// b. If Type(_V_) is not Object, throw a *TypeError* exception.
if (!(typeof V === "object" && V !== null || typeof V === "function"))
throw new TypeError("Object expected");
// c. Let _resource_ be ? CreateDisposableResource(_V_, _hint_)/
resource = CreateDisposableResource(V, hint);
}
// 2. Else,
else {
// a. If _V_ is *null* or *undefined*, then
if (V === null || V === undefined) {
// i. Let _resource_ be CreateDisposableResource(*undefined*, _hint_, _method_).
resource = CreateDisposableResource(undefined, hint, method);
}
// b. Else,
else {
// i. If Type(_V_) is not Object, throw a *TypeError* exception.
if (!(typeof V === "object" && V !== null || typeof V === "function"))
throw new TypeError("Object expected");
// ii. Let _resource_ be CreateDisposableResource(_V_, _hint_, _method_).
resource = CreateDisposableResource(V, hint, method);
}
}
// 3. Append _resource_ to _disposable_.[[DisposableResourceStack]].
disposableResourceStack[disposableResourceStack.length] = resource;
// 4. Return NormalCompletion(~empty~).
}
/* @internal */
export function CreateDisposableResource(V, hint, method) {
// 3.1.3 CreateDisposableResource ( _V_, _hint_ [, _method_ ] )
// 1. If _method_ is not present, then
if (!method) {
// a. If _V_ is *undefined*, throw a *TypeError* exception.
if (V === undefined)
throw new TypeError("Object expected");
// b. Set _method_ to ? GetDisposeMethod(_V_, _hint_).
method = GetDisposeMethod(V, hint);
// c. If _method_ is *undefined*, throw a *TypeError* exception.
if (method === undefined)
throw new TypeError(hint === "async" ? "Object not async disposable" : "Object not disposable");
}
// 2. Else,
else {
// a. If IsCallable(_method_) is *false*, throw a *TypeError* exception.
if (!(typeof method === "function"))
throw new TypeError(hint === "async" ? "Object not async disposable" : "Object not disposable");
}
// 3. Return the DisposableResource Record { [[ResourceValue]]: _V_, [[Hint]]: _hint_, [[DisposeMethod]]: _method_ }.
return { resource: V, hint, dispose: method };
}
/* @internal */
export function GetDisposeMethod(V, hint) {
// 3.1.4 GetDisposeMethod ( _V_, _hint_ )
let method;
// 1. If _hint_ is ~async~, then
if (hint === "async") {
// a. Let _method_ be ? GetMethod(_V_, @@asyncDispose).
// b. If _method_ is *undefined*, then
// i. Set _method_ to ? GetMethod(_V_, @@dispose).
method = GetMethod(V, asyncDisposable_js_1.AsyncDisposable.asyncDispose);
if (method === undefined) {
method = GetMethod(V, disposable_js_1.Disposable.dispose);
}
}
// 2. Else,
else {
// a. Let _method_ be ? GetMethod(_V_, @@dispose).
method = GetMethod(V, disposable_js_1.Disposable.dispose);
}
// 3. Return _method_.
return method;
}
/* @internal */
export function Dispose(V, hint, method) {
// 3.1.5 Dispose ( _V_, _hint_, _method_ )
return hint === "async" ?
DisposeAsync(V, method) :
DisposeSync(V, method);
}
function DisposeSync(V, method) {
// 3.1.5 Dispose ( _V_, _hint_, _method_ )
// NOTE: when _hint_ is ~sync~
// 1. [Let _result_ be] ? Call(_method_, _V_).
(0, Call)(method, V);
// 2. [If _hint_ is ~async~ and _result_ is not *undefined*, then]
// 3. Return *undefined*.
}
async function DisposeAsync(V, method) {
// 3.1.5 Dispose ( _V_, _hint_, _method_ )
// NOTE: when _hint_ is ~async~
// 1. Let _result_ be ? Call(_method_, _V_).
const result = (0, Call)(method, V);
// 2. If [_hint_ is ~async~ and] _result_ is not *undefined*, then
if (result !== undefined) {
await result;
}
// 3. Return *undefined*.
}
export function DisposeResources(hint, disposableResourceStack, throwCompletion, errors) {
// 3.1.6 DisposeResources ( _disposable_, _completion_, [ , _errors_ ] )
return hint === "async" ?
DisposeResourcesAsync(disposableResourceStack, throwCompletion, errors) :
DisposeResourcesSync(disposableResourceStack, throwCompletion, errors);
}
function DisposeResourcesSync(disposableResourceStack, throwCompletion, errors) {
// 3.1.6 DisposeResources ( _disposable_, _completion_, [ , _errors_ ] )
// NOTE: when _hint_ is ~sync~
// 1. If _errors_ is not present, let _errors_ be a new empty List.
errors !== null && errors !== void 0 ? errors : (errors = []);
// 2. If _disposable_ is not *undefined*, then
if (disposableResourceStack !== undefined) {
// a. For each _resource_ of _disposable_.[[DisposableResourceStack]], in reverse list order, do
for (let i = disposableResourceStack.length - 1; i >= 0; i--) {
const resource = disposableResourceStack[i];
// i. Let _result_ be Dispose(_resource_.[[ResourceValue]], _resource_.[[Hint]], _resource_.[[DisposeMethod]]).
try {
Dispose(resource.resource, resource.hint, resource.dispose);
}
catch (e) {
// 1. If _result_.[[Type]] is ~throw~, then
// a. Append _result_.[[Value]] to _errors_.
errors[errors.length] = e;
}
}
}
// 3. Let _errorsLength_ be the number of elements in _errors_.
// 4. If _errorsLength_ > 0, then
if (errors.length > 0) {
// a. Let _error_ be a newly created `AggregateError` object.
// b. Perform ! DefinePropertyOrThrow(_error_, *"errors"*, PropertyDescriptor { [[Configurable]]: *true*, [[Enumerable]]: *false*, [[Writable]]: *true*, [[Value]]: ! CreateArrayFromList(_errors_) }).
// c. If _completion_.[[Type]] is ~throw~, then
// i. Perform ! CreateNonEnumerableDataPropertyOrThrow(_error_, "cause", _completion_.[[Value]]).
// d. Return ThrowCompletion(_error_).
ThrowAggregateError(errors, throwCompletion, DisposeResources);
}
// 5. Return _completion_.
if (throwCompletion)
throw throwCompletion.cause;
}
async function DisposeResourcesAsync(disposableResourceStack, throwCompletion, errors) {
// 3.1.6 DisposeResources ( _disposable_, _completion_, [ , _errors_ ] )
// NOTE: when _hint_ is ~async~
// 1. If _errors_ is not present, let _errors_ be a new empty List.
errors !== null && errors !== void 0 ? errors : (errors = []);
// 2. If _disposable_ is not *undefined*, then
if (disposableResourceStack !== undefined) {
// a. For each _resource_ of _disposable_.[[DisposableResourceStack]], in reverse list order, do
for (let i = disposableResourceStack.length - 1; i >= 0; i--) {
const resource = disposableResourceStack[i];
// i. Let _result_ be Dispose(_resource_.[[ResourceValue]], _resource_.[[Hint]], _resource_.[[DisposeMethod]]).
try {
await Dispose(resource.resource, resource.hint, resource.dispose);
}
catch (e) {
// 1. If _result_.[[Type]] is ~throw~, then
// a. Append _result_.[[Value]] to _errors_.
errors[errors.length] = e;
}
}
}
// 3. Let _errorsLength_ be the number of elements in _errors_.
// 4. If _errorsLength_ > 0, then
if (errors.length > 0) {
// a. Let _error_ be a newly created `AggregateError` object.
// b. Perform ! DefinePropertyOrThrow(_error_, *"errors"*, PropertyDescriptor { [[Configurable]]: *true*, [[Enumerable]]: *false*, [[Writable]]: *true*, [[Value]]: ! CreateArrayFromList(_errors_) }).
// c. If _completion_.[[Type]] is ~throw~, then
// i. Perform ! CreateNonEnumerableDataPropertyOrThrow(_error_, "cause", _completion_.[[Value]]).
// d. Return ThrowCompletion(_error_).
ThrowAggregateError(errors, throwCompletion, DisposeResources);
}
// 5. Return _completion_.
if (throwCompletion)
throw throwCompletion.cause;
}
/* @internal */
export function CreateScope(hint) {
// Credit to Mathieu Hofman for initial `for (const { using } of Disposable)` mechanism: https://github.com/mhofman/disposator/
// See THIRD PARTY LICENSE NOTICE at the top of this file.
// Modified to return a `fail` callback to emulate error suppression semantics of https://github.com/tc39/proposal-explicit-resource-management/
const scope = {
using(resource) {
if (context.state !== "initialized")
throw new Error("Illegal state.");
AddDisposableResource(context.disposables, resource, hint);
return resource;
},
fail(error) {
if (context.state !== "initialized")
throw new Error("Illegal state.");
context.throwCompletion = { cause: error };
}
};
const context = {
scope: Object.freeze(scope),
state: "initialized",
disposables: [],
throwCompletion: undefined,
};
return context;
}
function ThrowAggregateError(errors, throwCompletion, stackCrawlMark = ThrowAggregateError) {
let error;
if (typeof AggregateError === "function") {
error = new AggregateError(errors);
}
else {
error = new Error("One or more errors occurred");
error.name = "AggregateError";
error.errors = errors;
}
if (throwCompletion) {
Object.defineProperty(error, "cause", { configurable: true, writable: true, value: throwCompletion.cause });
}
if (Error.captureStackTrace) {
Error.captureStackTrace(error, stackCrawlMark);
}
throw error;
}