Skip to content

If fn in stack.defer() throws or returns rejected promise, one gets Cannot read properties of undefined (reading '?') #9

@azerum

Description

@azerum

Minimal TS code:

import 'disposablestack/auto'

async function main() {
    await using stack = new AsyncDisposableStack()    
    
    stack.defer(() => {
        console.log('1')
    })
    
    stack.defer(() => {
        throw new Error('2')
    })
}

void main()
Compiled to JS
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
    if (value !== null && value !== void 0) {
        if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
        var dispose, inner;
        if (async) {
            if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
            dispose = value[Symbol.asyncDispose];
        }
        if (dispose === void 0) {
            if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
            dispose = value[Symbol.dispose];
            if (async) inner = dispose;
        }
        if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
        if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
        env.stack.push({ value: value, dispose: dispose, async: async });
    }
    else if (async) {
        env.stack.push({ async: true });
    }
    return value;
};
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
    return function (env) {
        function fail(e) {
            env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
            env.hasError = true;
        }
        function next() {
            while (env.stack.length) {
                var rec = env.stack.pop();
                try {
                    var result = rec.dispose && rec.dispose.call(rec.value);
                    if (rec.async) return Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
                }
                catch (e) {
                    fail(e);
                }
            }
            if (env.hasError) throw env.error;
        }
        return next();
    };
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
    var e = new Error(message);
    return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
});
import 'disposablestack/auto';
async function main() {
    const env_1 = { stack: [], error: void 0, hasError: false };
    try {
        const stack = __addDisposableResource(env_1, new AsyncDisposableStack(), true);
        stack.defer(() => {
            console.log('1');
        });
        stack.defer(() => {
            throw new Error('2');
        });
    }
    catch (e_1) {
        env_1.error = e_1;
        env_1.hasError = true;
    }
    finally {
        const result_1 = __disposeResources(env_1);
        if (result_1)
            await result_1;
    }
}
void main();

I would expect:

  • 1 to be printed
  • Disposal of stack to throw the created new Error('2')

What actually happens (full output):

<redacted-path>/node_modules/.pnpm/disposablestack@1.1.7/node_modules/disposablestack/AsyncDisposableStack/implementation.js:83
			return completion['?'](); // step 5
			                 ^

TypeError: Cannot read properties of undefined (reading '?')
    at <redacted-path>/node_modules/.pnpm/disposablestack@1.1.7/node_modules/disposablestack/AsyncDisposableStack/implementation.js:83:21
    at main (<redacted-path>:4:5)

Note that making the throwing lambda async gives the same error

Versions:

  • disposablestack: 1.1.7

  • typescript: 5.5.4

  • Node.js: 20.16.0

  • tsconfig:

"compilerOptions": {
     "module": "Node16",
     "moduleResolution": "Node16",
     "lib": ["ES2022", "ESNext.Disposable"],
     "target": "ES2022"
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions