Node.js文件的异步和同步读写 - 何时使用哪种?

4

这是一个非常普遍的问题,但我不太理解。在什么情况下我会更喜欢其中的一种呢?我似乎不明白可能会出现的情况,哪一种会更有利。是否存在强烈的理由避免使用x / 使用x?


同样的一般性答案是:任何同步操作都会阻塞其他所有操作。一个不太一般的例子是:如果你有一个 Web 服务器并且同步读取一个文件,那么在该文件被读取之前,没有请求将被处理。因此:除了启动时的一次性事情外,避免阻塞,并尤其避免在 I/O 上阻塞,因为它需要很长时间,而且有时是不可预测的。 - Ry-
非常感谢,这使得问题更加清晰了,例如Web服务器和传入请求的示例。我之前考虑过使用带有Node.js的Electron应用程序,它只是读取文件,但我真的不明白这会有什么区别,如果我的软件确实只需要等待该文件加载。非常感谢。 - George Welder
@GeorgeWelder:啊,Electron。我已经在我的答案中添加了一条注释来尝试解决那个问题,但是很遗憾我对Electron的了解很少。 - T.J. Crowder
2个回答

3
在一个旨在扩展并为许多用户提供服务的服务器中,仅在服务器初始化期间使用同步I/O。实际上,`require()`本身就使用同步I/O。在服务器已经运行并处理传入请求的所有其他部分中,您仅会使用异步I/O。
除了创建服务器之外,node.js还有其他用途。例如,假设您想创建一个脚本,该脚本将解析巨大的文件并查找特定的词或短语。此脚本是设计为自己运行以处理一个文件,并且它没有持久化服务器功能,并且没有特定原因要同时从多个来源进行I/O。在这种情况下,可以完全使用同步I/O。例如,我创建了一个node.js脚本,帮助我年龄备份文件(删除满足某些特定年龄标准的备份文件),我的计算机每天自动运行该脚本。对于这种类型的用途,没有理由使用异步I/O,因此我使用了同步I/O,这使得代码更简单。
请避免在服务器的请求处理程序中使用同步I/O。由于node.js中Javascript的单线程性质,使用同步I/O会阻塞node.js Javascript线程,因此它只能一次执行一项任务(对于多用户服务器来说是致命的),而异步I/O不会阻塞node.js Javascript线程(使其潜在地满足许多用户的需求)。
在非多用户情况下(仅为一位用户执行一项任务的代码),可能会更喜欢同步I/O,因为编写代码更容易,而且使用异步I/O可能没有任何优势。
如果这是一个单用户应用程序,并且在等待文件读入内存时没有其他要执行的任务(没有要响应的套接字,没有屏幕更新,没有其他请求要处理,并行运行的其他文件操作),那么使用异步I/O没有优势,因此同步I/O将很好并且可能更简单。

2
什么情况下应该选择其中一个而不是另一个?
除非在等待 I/O 期间没有其他任务需要执行,否则请使用非 Sync 版本(即 async 版本)。如果确实如此,请参见下文了解详情...
避免 x / 使用 x 的强烈理由是什么?
是的。NodeJS 在单个线程上运行 JavaScript 代码。如果您使用 I/O 函数的 Sync 版本,则该线程将被阻塞等待 I/O,并且无法执行任何其他操作。如果使用异步版本,则 I/O 可以在后台继续进行,而 JavaScript 线程可以继续执行其他工作;I/O 完成将作为作业排队,供 JavaScript 线程稍后返回。
如果您正在运行一个前台 Node 应用程序,而在等待 I/O 期间不需要执行任何其他操作,则可能可以使用 Sync 调用。但是,如果要使用 Node 处理多个事物(例如 Web 请求),最好使用异步版本。
在您在问题下添加的评论中,您已经说:
我考虑使用Node.js编写一个Electron应用程序,它只是读取一个文件,但我不明白这会有什么区别,因为我的软件无论如何都必须等待该文件加载。
我几乎没有关于Electron的了解,但我注意到它使用一个“主”进程来管理窗口,然后每个窗口都有一个“渲染”进程(link)。在这种情况下,使用Sync函数将阻塞相关进程,这可能会影响应用程序或窗口的响应能力。但我对Electron没有深入的了解(真可惜)。
直到最近,使用异步函数意味着使用大量回调密集型代码,这些代码很难组合。
// (Obviously this is just an example, you wouldn't actually read and write a file this way, you'd use streaming...)
fs.readFile("file1.txt", function(err, data) {
    if (err) {
        // Do something about the error...
    } else {
        fs.writeFile("file2.txt", data, function(err) {
            if (err) {
                // Do something about the error...
            } else {
                // All good
        });
    }
});

然后出现了 Promise,如果您使用操作的 promisified* 版本(在此示例中使用假名,如 fs.promisifiedXYZ),它仍涉及回调,但它们更具可组合性:
// (See earlier caveat, just an example)
fs.promisifiedReadFile("file1.txt")
.then(function(data) {
    return fs.promisifiedWriteFile("file2.txt", data);
})
.then(function() {
    // All good
})
.catch(function(err) {
    // Do something about the error...
});

现在,在Node的最新版本中,您可以使用ES2017+的async/await语法来编写看起来同步的代码,实际上是异步的:
// (See earlier caveat, just an example)
(async () => {
    try {
        const data = await fs.promisifiedReadFile("file1.txt");
        fs.promisifiedWriteFile("file2.txt", data);
        // All good
    } catch (err) {
        // Do something about the error...
    }
})();

Node的API先于Promise并有其自己的约定。有各种库可帮助您将一个Node风格的回调API“promisify”,以便它使用Promise。其中之一是 promisify ,但还有其他库。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接