我很困惑为什么我的承诺会阻止节点应用程序请求。
这是我的简化代码:
var express = require('express');
var someModule = require('somemodule');
app = express();
app.get('/', function (req, res) {
res.status(200).send('Main');
});
app.get('/status', function (req, res) {
res.status(200).send('Status');
});
// Init Promise
someModule.doSomething({}).then(function(){},function(){}, function(progress){
console.log(progress);
});
var server = app.listen(3000, function () {
var host = server.address().address;
var port = server.address().port;
console.log('Example app listening at http://%s:%s in %s environment',host, port, app.get('env'));
});
模块:
var q = require('q');
function SomeModule(){
this.doSomething = function(){
return q.Promise(function(resolve, reject, notify){
for (var i=0;i<10000;i++){
notify('Progress '+i);
}
resolve();
});
}
}
module.exports = SomeModule;
显然这很简单。 promise函数执行一些需要5到30分钟的工作,并且必须仅在服务器启动时运行。 该promise函数中有 NO 异步操作。它只是很多数据处理,循环等。
我不能马上做请求。所以我期望的是,当我运行服务器时,我可以立即转到127.0.0.1:3000
并查看Main
,并查看其他任何请求。
最终我希望通过访问/status
来查看该任务的进度,但我确信一旦服务器按预期工作,我就可以完成这项工作。
目前,当我打开/
时,它会一直挂起,直到承诺工作完成。
显然我做错了......
答案 0 :(得分:1)
node.js中Javascript的主线程是单线程的。所以,如果你做了一个处理器绑定的巨型循环,那么这将占用一个线程,并且没有其他JS将在node.js中运行,直到完成一个操作。
所以,当你打电话:
someModule.doSomething()
并且这都是同步的,然后它在完成执行之前不会返回,因此执行之后的代码行不会执行,直到doSomething()
方法返回。并且,正如您所理解的那样,使用具有同步CPU占用代码的承诺根本无助于您的事业。如果它是同步的并且受CPU限制,那么在其他任何东西运行之前它将需要很长时间才能运行。
如果循环中存在I / O(如磁盘I / O或网络I / O),则有机会使用异步I / O操作并使代码无阻塞。但是,如果没有,它只是很多CPU的东西,那么它将阻塞直到完成,没有其他代码将运行。
您改变这种情况的机会是:
在另一个进程中运行CPU使用代码。创建一个单独的程序,作为子进程运行,您可以将输入传递给输出或从中获取输出,或创建一个单独的服务器,然后您可以向其发出异步请求。
将非阻塞工作分解为一次执行100ms工作块的块,然后将处理器返回到事件循环(使用setTimeout()
之类的东西来允许事件中的其他事情在你选择并运行下一部分工作之前,队列要进行维护和运行。你可以看到Best way to iterate over an array without blocking the UI关于如何进行同步工作的想法。
例如,您可以将当前循环分块。这会运行长达100毫秒的周期,然后中断执行以使其他事物有机会运行。您可以将循环时间设置为您想要的任何值。
function SomeModule(){
this.doSomething = function(){
return q.Promise(function(resolve, reject, notify){
var cntr = 0, numIterations = 10000, timePerSlice = 100;
function run() {
if (cntr < numIterations) {
var start = Date.now();
while (Date.now() - start < timePerSlice && cntr < numIterations) {
notify('Progress '+cntr);
++cntr;
}
// give some other things a chance to run and then call us again
// setImmediate() is also an option here, but setTimeout() gives all
// other operations a chance to run alongside this operation
setTimeout(run, 10);
} else {
resolve();
}
}
run();
});
}
}