Node.js / express: немедленно ответить на запрос клиента и продолжить задачи в следующем окне
Я хотел бы отделить задачу процессора с высокой нагрузкой на сервер от пользовательского опыта:
./main.js:
var express = require('express');
var Test = require('./resources/test');
var http = require('http');
var main = express();
main.set('port', process.env.PORT || 3000);
main.set('views', __dirname + '/views');
main.use(express.logger('dev'));
main.use(express.bodyParser());
main.use(main.router);
main.get('/resources/test/async', Test.testAsync);
main.configure('development', function() {
main.use(express.errorHandler());
});
http.createServer(main).listen(main.get('port'), function(){
console.log('Express server app listening on port ' + main.get('port'));
});
./resources/test.js:
function Test() {}
module.exports = Test;
Test.testAsync = function(req, res) {
res.send(200, "Hello world, this should be sent inmediately");
process.nextTick(function() {
console.log("Simulating large task");
for (var j = 0; j < 1000000000; j++) {
// Simulate large loop
}
console.log("phhhew!! Finished!");
});
};
При запросе "localhost:3000/resources/test/async" я ожидал, что браузер отобразит "Hello world, это должно быть отправлено очень быстро", а node.js продолжит обработку, и через некоторое время в консоли появится сообщение "done",
Вместо этого браузер продолжает ждать, пока node.js завершит большую задачу, а затем отобразит содержимое. Я пробовал с res.set({ 'Connection': 'close' });
а также res.end();
но ничего не работает, как ожидалось. Я также погуглил без удачи.
Как должно быть, чтобы отправить ответ клиенту немедленно, а сервер продолжил выполнение задач?
РЕДАКТИРОВАТЬ
метод разветвленной вилки в решении
3 ответа
Спасибо за помощь Питеру Лайонсу, наконец, главной проблемой был буфер Firefox: ответ был не таким длинным, чтобы его очистить (поэтому Firefox продолжал ждать).
В любом случае, для высокопроизводительных задач, выполняемых ЦП, узел будет зависать до завершения, поэтому не будет обслуживать новые запросы. Если кому-то это нужно, это можно сделать с помощью разветвления (с помощью child_process, см. Пример в http://nodejs.org/api/child_process.html).
Должен сказать, что изменение контекста с помощью разветвления может занять больше времени, чем разделение задачи на разные такты.
./resources/test.js:
var child = require('child_process');
function Test() {}
module.exports = Test;
Test.testAsync = function(req, res) {
res.send(200, "Hello world, this should be sent inmediately");
var childTask = child.fork('child.js');
childTask.send({ hello: 'world' });
};
./resources/child.js:
process.on('message', function(m) {
console.log('CHILD got message:', m);
});
Попробуйте подождать вместо загрузки процессора:
res.send("Hello world, this should be sent inmediately");
console.log("Response sent.");
setTimeout(function() {
console.log("After-response code running!");
}, 3000);
node.js является однопоточным. Если вы заблокируете процессор занятым циклом, все это останавливается, пока это не будет сделано.
Хорошим решением является использование child_process.fork()
: он позволяет вам выполнить другой файл JavaScript вашего приложения в другом экземпляре Node и, таким образом, в другом цикле событий. Конечно, вы все равно можете обмениваться данными между двумя процессами, отправляя сообщения: так, из вашего процесса пользовательского интерфейса вы можете отправить сообщение разветвленному процессу, чтобы попросить его выполнить что-то.
Например, в ui.js
:
var ChildProcess = require('child_process');
var heavyTaskWorker = ChildProcess.fork('./heavyTaskWorker.js');
...
var message = {
operation: "longOperation1",
parameters: {
param1: "value1",
...
}
};
heavyTaskWorker.send(message);
И в heavyTaskWorker.js
:
process.on('message', function (message) {
switch (message.operation) {
case 'longOperation1':
longOperation1.apply(null, message.parameters);
break;
...
}
});
Проверено здесь, и работает отлично!
Надеюсь, это поможет!