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;
    ...
    }
});

Проверено здесь, и работает отлично!

Надеюсь, это поможет!

Другие вопросы по тегам