我的一般问题是我可以使用什么技术来确保在 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/