在非回调函数上调用 promisify():“有趣”的结果是节点。为什么?

Mer*_*erc 4 node.js promise async-await es6-promise

我在 node 的 promisify() 函数中发现了一个奇怪的行为,我无法弄清楚它为什么这样做。

考虑以下脚本:

#!/usr/bin/env node

/**
 * Module dependencies.
 */

var http = require('http')

var promisify = require('util').promisify

;(async () => {
  try {

    // UNCOMMENT THIS, AND NODE WILL QUIT
    // var f = function () { return 'Straight value' }
    // var fP = promisify(f)
    // await fP()

    /**
     * Create HTTP server.
     */
    var server = http.createServer()

    /**
     * Listen on provided port, on all network interfaces.
     */

    server.listen(3000)
    server.on('error', (e) => { console.log('Error:', e); process.exit() })
    server.on('listening', () => { console.log('Listening') })
  } catch (e) {
    console.log('ERROR:', e)
  }
})()

console.log('OUT OF THE ASYNC FUNCTION')
Run Code Online (Sandbox Code Playgroud)

这是一个简单的自调用函数,用于启动节点服务器。这很好。

现在...如果您取消注释“UNCOMMENT THIS”下的行,节点将退出而不运行服务器。

我知道我在一个调用回调但返回一个值的函数上使用 promisify() 。所以,我知道这本身就是一个问题。

但是......为什么节点只是退出......?

这真的很难调试——尤其是当你有比小脚本更复杂的东西时。

如果将函数定义更改为实际调用回调的内容:

var f = function (cb) { setTimeout( () => { return cb( null, 'Straight value') }, 2000) }
Run Code Online (Sandbox Code Playgroud)

一切都按预期工作......

更新

极大的简化:

function f () {
  return new Promise(resolve => {
    console.log('AH')
  })
}

f().then(() => {
  console.log('Will this happen...?')
})
Run Code Online (Sandbox Code Playgroud)

只会打印“AH”!

jfr*_*d00 5

在非回调函数上调用 promisify():“有趣”的结果是 node. 为什么?

因为你允许 node.js 无所事事地进入事件循环。由于没有实时的异步操作,也没有更多的代码要运行,node.js 意识到没有其他事情可做,也没有办法运行其他任何东西,所以它退出了。

当你点击await并且 node.js 回到事件循环时,没有任何东西保持 node.js 运行,所以它退出了。没有计时器或打开的套接字或任何保持 node.js 运行的类型的东西,所以 node.js 自动退出检测逻辑说没有其他事情要做,所以它退出。

因为 node.js 是一个事件驱动的系统,如果你的代码返回到事件循环并且没有任何类型的异步操作(打开套接字、侦听服务器、定时器、文件 I/O 操作、其他硬件侦听器等) ...),则没有任何运行可以在事件队列中插入任何事件,并且队列当前为空。因此,node.js 意识到永远不可能在此应用程序中运行更多代码,因此它退出。这是 node.js 内置的自动行为。

内部真正的异步操作fp()将具有某种套接字或计时器或保持进程运行的打开状态。但是因为你的是假的,所以没有任何东西可以保持 node.js 运行。

如果在 中放置setTimeout()1 秒f(),您将看到进程退出发生在 1 秒后。所以,进程退出与promise无关。这与您已经返回到事件循环这一事实有关,但您还没有启动任何可以保持 node.js 运行的操作。

或者,如果将 asetInterval()放在 async 函数的顶部,您将同样发现该进程不会退出。


因此,如果您这样做,也会类似地发生这种情况:

var f = function () { return 'Straight value' }
var fP = promisify(f);
fP().then(() => {
    // start your server here
});
Run Code Online (Sandbox Code Playgroud)

或这个:

function f() {
    return new Promise(resolve => {
       // do nothing here
    });
}

f().then(() => {
    // start your server here
});
Run Code Online (Sandbox Code Playgroud)

问题不在于promisify()操作。这是因为您正在等待一个不存在的异步操作,因此 node.js 无事可做,它注意到无事可做,所以它会自动退出。使用.then()处理程序公开承诺并不能保持 node.js 运行。而是需要一些活动的异步操作(计时器、网络套接字、侦听服务器、正在进行的文件 I/O 操作等...)来保持 node.js 运行。

在这种特殊情况下,node.js 本质上是正确的。你的承诺永远不会解决,没有其他东西排队运行,因此你的服务器永远不会启动,你的应用程序中的其他代码永远不会运行,因此继续运行实际上没有用。您的代码无事可做,也无法真正做其他任何事情。

如果将函数定义更改为实际调用回调的内容:

那是因为你使用了一个计时器,所以 node.js 在等待 promise 解决时有一些实际的事情要做。没有.unref()调用它的正在运行的计时器将阻止自动退出。

值得一读:node.js 进程如何知道何时停止?


仅供参考,您可以“关闭”或“绕过”node.js 自动退出逻辑,只需将其添加到启动代码的任何位置即可:

// timer that fires once per day
let foreverInterval = setInterval(() => {
    // do nothing
}, 1000 * 60 * 60 * 24);
Run Code Online (Sandbox Code Playgroud)

这总是给 node.js 一些事情要做,所以它永远不会自动退出。然后,当您确实希望您的进程退出时,您可以调用clearInterval(foreverInterval)或仅使用process.exit(0).