Javascript 在Node.js中编写非阻塞函数的正确方法
我写了一个简单的函数,返回Promise,所以应该是非阻塞的(在我看来)。不幸的是,该计划似乎不再等待承诺完成。我不确定这里会出什么问题Javascript 在Node.js中编写非阻塞函数的正确方法,javascript,node.js,promise,Javascript,Node.js,Promise,我写了一个简单的函数,返回Promise,所以应该是非阻塞的(在我看来)。不幸的是,该计划似乎不再等待承诺完成。我不确定这里会出什么问题 function longRunningFunc(val, mod) { return new Promise((resolve, reject) => { sum = 0; for (var i = 0; i < 100000; i++) { for (var j = 0; j &l
function longRunningFunc(val, mod) {
return new Promise((resolve, reject) => {
sum = 0;
for (var i = 0; i < 100000; i++) {
for (var j = 0; j < val; j++) {
sum += i + j % mod
}
}
resolve(sum)
})
}
console.log("before")
longRunningFunc(1000, 3).then((res) => {
console.log("Result: " + res)
})
console.log("after")
但是程序会在打印第二行和第三行之前等待。你能解释一下,先打印“之前”和“之后”,然后(在一段时间后)打印结果的正确方法是什么吗?将代码包装在承诺中(就像你所做的那样)并不会使它成为非阻塞的。Promise executor函数(传递给
new Promise(fn)
的回调)是同步调用的,将被阻塞,这就是您看到输出延迟的原因
事实上,没有办法创建自己的纯Javascript代码(就像您所拥有的那样)这是无阻塞的,除了将其放入子进程、使用WorkerThread、使用创建新Javascript线程的第三方库或使用新的实验node.js API进行线程外。常规node.js以阻塞和单线程的方式运行Javascript,无论它是否包装在承诺中
您可以使用setTimeout()
之类的方法来更改代码运行的“时间”,但每当代码运行时,它仍然会阻塞(一旦开始执行,在完成之前,其他任何操作都无法运行)。node.js库中的异步操作都使用某种形式的底层本机代码,允许它们异步(或者他们只使用其他node.js异步API,这些API本身使用本机代码实现)
但是程序在打印第二行和第三行之前会等待。你能解释一下,首先打印“之前”和“之后”,然后(经过一段时间)打印结果的正确方法是什么吗
正如我上面所说的,在promise executor函数中封装东西并不会使它们异步。如果你想“改变”事情运行的时间(虽然它们仍然是同步的),你可以使用setTimeout()
,但这并不是真的让任何事情都不阻塞,它只是让它在以后运行(运行时仍然阻塞)
所以,你可以这样做:
function longRunningFunc(val, mod) {
return new Promise((resolve, reject) => {
setTimeout(() => {
sum = 0;
for (var i = 0; i < 100000; i++) {
for (var j = 0; j < val; j++) {
sum += i + j % mod
}
}
resolve(sum)
}, 10);
})
}
函数longRunningFunc(val,mod){
返回新承诺((解决、拒绝)=>{
设置超时(()=>{
总和=0;
对于(变量i=0;i<100000;i++){
对于(var j=0;j
这将重新安排耗时的循环,使其在以后运行,并且可能“看起来”是非阻塞的,但实际上它仍然会阻塞—它只是在以后运行。要使其真正非阻塞,您必须使用前面提到的技术之一将其从主Javascript线程中释放出来
在node.js中创建实际非阻塞代码的方法:
在单独的子进程中运行它,并在完成时获得异步通知
在node.js v11中使用新的实验
在node.js中编写自己的本机代码插件,并在实现中使用libuv线程或操作系统级线程(或其他操作系统级异步工具)
构建在以前存在的异步API之上,并且没有在主线程中花费很长时间的代码
promise的executor函数是同步运行的,这就是代码阻塞执行主线程的原因
为了不阻塞执行的主线程,您需要在执行长时间运行的任务时周期性地、协同地产生控制。实际上,您需要将任务拆分为子任务,然后在事件循环的新标记上协调子任务的运行。这样,您就可以为其他任务分配控制权(如呈现和响应用户输入)运行的机会
您可以使用promise API编写自己的异步循环,也可以使用异步函数。异步函数支持函数的挂起和重新生成(可重入),并隐藏大部分复杂性
下面的代码使用setTimeout
将子任务移动到新的事件循环标记上。当然,这可以推广,批处理可以用于在任务进度和UI响应性之间找到平衡;此解决方案中的批处理大小仅为1,因此进度缓慢
最后:这类问题的真正解决方案可能是一个解决方案
const$=document.querySelector.bind(文档)
常量大号=1000
让计数=0
//请注意,这也可以使用requestIdleCallback或requestAnimationFrame
const tick=(fn)=>新承诺((解析)=>设置超时(()=>解析(fn),5))
异步函数longRunningTask(){
而(count++console.log(`***已完成***`))
$('button')。onclick=()=>$('#output')。innerHTML+=`当前计数为:${count}
`
*{
字号:16pt;
颜色:灰色;
填充:15px;
}
单击我以查看UI是否仍有响应。
好吧,如果你想在“一段时间后”真正测试,你可以在你的解析(sum)周围加上一个setTimeout声明。这是行不通的。你只为你的代码得到一个线程。在承诺或超时中打包同步代码不会改变。如果你想编写异步代码,你需要创建一个相关的:对于客户端,也有用于创建单独线程任务的。对于C++插件中的OS级线程来说,已经相当不错了。根据我的经验,我个人使用它进行实时图像处理,在非阻塞线程中进行对象检测,然后Node.js将检测到的质心分发给连接的TCP客户端(该客户端使用数据驱动电机向检测到的对象移动)。
function longRunningFunc(val, mod) {
return new Promise((resolve, reject) => {
setTimeout(() => {
sum = 0;
for (var i = 0; i < 100000; i++) {
for (var j = 0; j < val; j++) {
sum += i + j % mod
}
}
resolve(sum)
}, 10);
})
}