【问题标题】:FileStream Promise resolving earlyFileStream Promise 尽早解决
【发布时间】:2018-10-04 10:10:43
【问题描述】:

我在 nodeJS 中遇到了一个相当奇怪的问题,我不知道为什么。

考虑以下代码:

(async () => {
    console.log ("1");

    await new Promise ((resolve, reject) => {
        setTimeout (() => {
            console.log ("2");
            resolve ();
        }, 1000);
    });

    console.log ("3");
    process.exit ();
})();

这段代码完全按照它应该做的。它按该顺序打印123。在打印1 之后,它会等待大约一秒钟。完美的。现在让我们看下面的例子:

const fs = require ("fs");

(async () => {
    const stream = fs.createWriteStream ("file.txt");
    stream.write ("Test");

    console.log ("1");

    await new Promise ((resolve, reject) => {
        stream.on ("finish", () => {
            console.log ("2");
            resolve ();
        });
    });

    console.log ("3");
    process.exit ();
})();

据我了解,此代码应该完成,或者 - 如果 finish 事件永远不会被触发 - 无限运行。发生的情况正好相反:它打印1,然后退出。它不应该至少在退出之前打印另一个3,因为这是脚本的结尾吗?

重要:我知道承诺不会解决,因为.end() 没有在流上调用。我想知道为什么脚本还是完成了。

谁能向我解释这种行为?

【问题讨论】:

  • "end" 通常是事件。这样做也是一种不好的做法。如果您必须在 Promise 中包装事件处理程序,则创建实例并在包装 Promise 的“内部”注册所有处理程序。
  • 实际上,可写流需要finish-event,而可读流需要end-event,所以这应该不是问题。
  • 对不起,反着读。我看到您的误解,因为您似乎认为事情要等到您真正明确地关闭文件句柄。他们没有。
  • 我想我不明白你在这里。承诺应该等到它被解决。它永远不会被解决,否则,我们会看到 2 事先打印出来。为什么脚本不等待承诺就退出了?
  • 不。阅读我的回答,希望这是一个合理的演示。

标签: node.js promise stream


【解决方案1】:

最好的解释可能是在不使用 async/await 关键字的情况下编写此代码,以便您了解这些不会做任何“神奇”的事情,而只是“糖”,以不同的方式解决 Promise,而不是 @987654323 @。

const fs = require ("mz/fs");

const stream = fs.createWriteStream("file.txt");
stream.write("Test");

console.log("1");

new Promise ((resolve, reject) => {
    stream.on ("finish", () => {
        console.log("2");
        resolve();
    });
}).then(() => {
   console.log("2");
   process.exit();
});

这完全一样!那么问题在哪里。

您真正缺少的是,当您打开文件句柄时,“没有任何东西”表明它“必须”在程序退出之前显式关闭。因此,“无需等待”,程序完成但不会“分支”到仍在等待 Promiseresolve() 的部分。

它只记录 "1" 的原因是因为剩余的分支“正在”等待 Promise 解决,但在程序完成之前它永远不会到达那里。

当然,当您在 write 之后立即实际调用 stream.end() 或理想情况下通过“等待”任何可能挂起的写入请求时,所有这些都会发生变化:

const fs = require ("mz/fs");

(async () => {
    const stream = fs.createWriteStream ("file.txt");
    await stream.write ("Test");          // await here before continuing
    stream.end()
    console.log ("1");

    await new Promise ((resolve, reject) => {
        stream.on ("finish", () => {
            console.log ("2");
            //resolve ();
        });
    });

    console.log ("3");
    //process.exit ();
})();

这当然会记录列表中的每个输出,您应该很清楚。

所以,如果您希望在日志中看到 "3",原因是 await 我们从不关闭流。摆脱await

const fs = require ("mz/fs");

(async () => {
    const stream = fs.createWriteStream ("file.txt");
    await stream.write ("Test");
    stream.end()
    console.log ("1");

    new Promise ((resolve, reject) => {    // remove await - execution hoisted
        stream.on ("finish", () => {
            console.log ("2");
            //resolve ();
        });
    });

    console.log ("3");
    //process.exit ();
})();

那么你“应该”看到:

1
3
2

至少在大多数系统上,除非您有“极端”滞后。但一般来说,"finish" 应该在“等待”write 之后到达下一行之前被解雇。


注意:这里只使用mz 库来演示write() 方法上的await,而不包装回调。一般来说,回调执行应该解决同样的问题。

【讨论】:

  • 感谢您的解释。这是否意味着当 Promise 没有解决时,程序将在没有更多事情可做时直接退出,而不是等待无限长的时间?
  • @NikxDa 这取决于发生了什么。例如,http 服务器实际上仍在监听事件循环中的内容,或者“读取”文件正在等待文件结束。这样的事情意味着程序在其中任何一个停止之前都不会退出。但是没有任何内容表明您打开的用于写入的文件需要完成。或者确实Promise 本身必须在完成之前解决。如果是这种情况,它就会被丢弃。
  • 感谢您的信息,这是有道理的!我以为Promises 无论如何都会等待解决。
  • @NikxDa 这就是为什么我使用await 关键字“不使用”开始解释的原因。常见的误解是这个关键字告诉一切“停止”。这不是“隐蔽到同步”的魔法。只是一种不同的写作方式.then(),而且更干净。但经常被误解。
  • 感谢您的帮助!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-04-06
  • 2017-10-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-21
相关资源
最近更新 更多