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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在非回调函数上调用promisify():节点中的“有趣”结果。为什么?相关的知识,希望对你有一定的参考价值。

我在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')

它是一个简单的自调用函数,可以启动节点服务器。那没关系。

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

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

但是......为什么节点才会退出......?

这真的很难调试 - 特别是当你有一个比一个小脚本更复杂的东西时。

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

var f = function (cb) { setTimeout( () => { return cb( null, 'Straight value') }, 2000) }

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

UPDATE

巨大的简化:

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

f().then(() => {
  console.log('Will this happen...?')
})

只打印“AH”!

答案

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

因为您允许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()中放置一个f() 1秒钟,你会发现1秒后进程退出。因此,流程退出与承诺无关。它与你已经回到事件循环这一事实有关,但你还没有启动任何会使node.js运行的东西。

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


所以,如果你这样做会同样发生:

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

或这个:

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

f().then(() => {
    // start your server here
});

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

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

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

那是因为你使用了一个计时器,所以node.js在等待解析的承诺时实际上有所作为。没有调用.unref()的运行计时器将阻止自动退出。

值得一读:How does a node.js process know when to stop?


仅供参考,您可以通过在启动代码中的任何位置添加它来“关闭”或“绕过”node.js自动退出逻辑:

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

这总是给node.js一些东西,所以它永远不会自动退出。然后,当你确实希望你的进程退出时,你可以调用clearInterval(foreverInterval)或只是强制使用process.exit(0)

以上是关于在非回调函数上调用promisify():节点中的“有趣”结果。为什么?的主要内容,如果未能解决你的问题,请参考以下文章

Promisifying xml2js 解析函数(ES6 Promises)

将 node-redis 与 node 8 util.promisify 一起使用

为啥 HTTPs 节点库中的回调被调用两次?如何预防?

如何将节点中的异步回调添加到函数调用?

Promisifying Mongoose Connect 中的类型错误

_util.default.promisify不是使用Node 9.5的函数