Javascript 自动释放资源(如 RAII)

标签 javascript raii

我的一般问题是我可以使用什么技术来确保在 Javascript 中清理/释放资源?目前,我正在采用 C(不使用 goto)方法在我的函数中查找返回或异常的每条执行路径,并确保进行清理。

我的具体示例是这样的:在 Node.js 中,我在对象成员函数中使用互斥锁(通过文件锁)(我需要互斥,因为我运行 Node.js 应用程序的多个实例并且在不同时有竞争条件实例与文件系统交互)。

例如,在 C++ 中,我会执行如下操作:

void MyClass::dangerous(void) {
     MyLock lock(&this->mutex);
     ...
     // at the end of this function, lock will be destructed and release this->mutex.
}

据我所知,JavaScript 不提供任何 RAII 功能。在 C 语言中,我会使用 goto 来在发生错误时展开我的资源分配,这样我只有一个函数返回路径。

有哪些技术可以在 Javascript 中实现类似的效果?

最佳答案

正如其他人可能已经注意到的,您需要使用 try/finally。创建一个包装器函数来模拟生命周期范围可能更适合来自 C++。尝试在 javascript 控制台中运行以下代码以获取其用法示例:

C++ 风格

class MockFileIO {
    constructor(path) {
        console.log("Opening file stream to path", path);
        this.path = path;
    }
    destructor() {
        console.log("Closing file stream to path", this.path);
    }
    write(str) {
        console.log("Writing to file: ", str);
    }
}

async function run_with(resource, func) {
    try {
        func(resource);
    } catch(e) {
        throw e;
    } finally {
        resource.destructor();
    }
}

async function main() {
    console.log("Starting program");
    const fpath = "somewhere.txt";
    await run_with(new MockFileIO(fpath), (f) => {
        f.write("hello");
        f.write("world");
    });
    console.log("returning from main");
}

main();

Golang 风格

从那以后,我找到了一种更适合我个人使用 javascript 的方式的范例。它基于 golang 的 defer 语句。您只需将代码包装在一个“范围”IIFE 中,当该函数由于任何原因被保留时,延迟表达式将以相反的顺序执行,等待任何 promise 。

用法:

scope(async (defer) => {
    const s = await openStream();
    defer(() => closeStream(s));

    const db = new DBConnection();
    defer(() => db.close());

    throw new Error("oh snap"); // could also be return

    // db.close() then closeStream(s)
});

范围可以返回值并且是异步的。下面是一个相同函数的示例,先不使用,然后使用 defer 技术:

// without defer
async function getUser() {
    const conn = new DB();
    const user = await conn.getUser();
    conn.close();
    return user;
}
// this is bad! conn.getUser could throw an error.

变成:

// with defer
async function getUser() {
    return await scope(async defer => {
        const conn = new DB();
        defer(() => conn.close());
        return await conn.getUser();
    });
}
// conn.close is always called, even after error.

基本上就是这样。范围也可以嵌套。定义范围的代码非常小:

async function scope(fn) {

    const stack = [];
    const defer = (action) => {
        stack.push(action);
    };
    const errs = [];

    try {
        return await fn(defer);
    } catch(e) {
        errs.push(e);
    } finally {
        while (stack.length) {
            try {
                await (stack.pop())();
            } catch(e) {
                errs.push(e);
            }
        }
        for (const e of errs.slice(1)) {
            await error("error in deferred action: " + e);
        }
        if (errs.length) {
            throw errs[0]; // eslint-disable-line
        }
    }
}

scope 立即执行回调并将所有延迟函数收集到堆栈中。当函数退出时(通过返回或错误),延迟堆栈被弹出,直到所有的延迟都被评估。延迟函数本身发生的任何错误都被收集到一个错误列表中,第一个错误在“作用域”退出时抛出。我已经在我为工作编写的一个非常关键、低容错的守护程序中使用了这种技术(实际上就是这些代码),它经受住了时间的考验。我希望这对遇到这种情况的任何人都有帮助。

关于Javascript 自动释放资源(如 RAII),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11693963/

相关文章:

javascript - 如何判断访问者是否通过手机访问网站?

javascript - jquery插件教程困惑,看不到初始化参数

javascript - 制作拖拽灵活分离器时的一个错误

c++ - 当 RAII 对象构造失败时

c++ - Windows HANDLE RAII管理,返回bool而不是句柄怎么办?

c++ - 函数类型不是模板非类型参数的有效类型?

javascript - 在页面加载时更改 css

javascript - 自动为图像添加灯箱效果

c++ - 初始化 std::auto_ptr: "error: no match for call to ‘(std::auto_ptr<int>) (int*)’ "

c++ - 在 C++11 的析构函数中锁定互斥量