正如其他人可能已经指出的那样,您需要使用 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 中,当该函数因任何原因离开时,延迟表达式将以相反的顺序执行,等待任何承诺。
用法:
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 立即执行回调并将所有延迟函数收集到堆栈中。当函数退出时(通过返回或错误),延迟堆栈被弹出,直到所有延迟都被评估。延迟函数本身发生的任何错误都会被收集到一个错误列表中,当“范围”退出时会抛出第一个错误。我已经在我为工作而编写的一个非常关键、容错率低的守护程序中使用了这种技术(实际上就是这段代码),它经受住了时间的考验。我希望这对遇到这种情况的人有所帮助。