7

To optimize the response delay, it is necessary to perform work after are response has been sent back to the client. However, the only way I can seem to get code to run after the response is sent is by using setTimeout. Is there a better way? Perhaps somewhere to plug in code after the response is sent, or somewhere to run code asynchronously?

Here's some code.

koa                  = require 'koa'
router               = require 'koa-router'

app = koa()

# routing
app.use router app

app
  .get '/mypath', (next) ->
    # ...
    console.log 'Sending response'

    yield next

    # send response???

    console.log 'Do some more work that the response shouldn\'t wait for'
ProgramFOX
  • 6,131
  • 11
  • 45
  • 51
Garrett
  • 11,451
  • 19
  • 85
  • 126
  • How do you send the request? There are usually `success` callbacks for this kind of thing – Brennan Oct 22 '14 at 20:04
  • 2
    Please show us your code. Usually, you'd just call `response.end()` to send/flush the response, and continue with your work. – Bergi Oct 22 '14 at 20:46
  • @Bergi `response.end()` isn't defined: https://github.com/koajs/koa/blob/master/docs/api/response.md. I use `koa-router`. I'll add some code. – Garrett Oct 24 '14 at 05:11
  • Thanks. But isn't that Coffeescript, not ES6? – Bergi Oct 24 '14 at 05:21
  • Coffeescript in ES6. I just forgot to yield. Will fix. Also, `response.end()` isnt supported by koa: https://github.com/koajs/koa/blob/master/docs/api/context.md#ctxres – Garrett Oct 24 '14 at 05:21
  • do a `setImmediate()`. if it "looks" synchronous in koa, it's going to block the request. – Jonathan Ong Oct 24 '14 at 06:31

4 Answers4

8

Do NOT call ctx.res.end(), it is hacky and circumvents koa's response/middleware mechanism, which means you might aswell just use express. Here is the proper solution, which I also posted to https://github.com/koajs/koa/issues/474#issuecomment-153394277

app.use(function *(next) {
  // execute next middleware
  yield next
  // note that this promise is NOT yielded so it doesn't delay the response
  // this means this middleware will return before the async operation is finished
  // because of that, you also will not get a 500 if an error occurs, so better log it manually.
  db.queryAsync('INSERT INTO bodies (?)', ['body']).catch(console.log)
})
app.use(function *() {
  this.body = 'Hello World'
})

No need for ctx.end()
So in short, do

function *process(next) {
  yield next;
  processData(this.request.body);
}

NOT

function *process(next) {
  yield next;
  yield processData(this.request.body);
}
Can Rau
  • 3,130
  • 1
  • 23
  • 33
felixfbecker
  • 2,273
  • 1
  • 19
  • 24
  • This is not a suitable solution if you run in a serverless environment because your process will be shut down while the asynchronous call is made – Masadow May 20 '22 at 17:40
0

I have the same problem.

koa will end response only when all middleware finish(In application.js, respond is a response middleware, it end the response.)

app.callback = function(){
  var mw = [respond].concat(this.middleware);
  var gen = compose(mw);
  var fn = co.wrap(gen);
  var self = this;

  if (!this.listeners('error').length) this.on('error', this.onerror);

  return function(req, res){
    res.statusCode = 404;
    var ctx = self.createContext(req, res);
    onFinished(res, ctx.onerror);
    fn.call(ctx).catch(ctx.onerror);
  }
};

But, we can make problem solved by calling response.end function which is node's api:

exports.endResponseEarly = function*(next){
    var res = this.res;
    var body = this.body;

    if(res && body){
        body = JSON.stringify(body);
        this.length = Buffer.byteLength(body);
        res.end(body);
    }

    yield* next;
};
Eason
  • 76
  • 5
  • I am looking at this same problem. I am not not completely sure I follow your answer though. What is the piece that continues after the response is sent? The next generator in the loop? – akaphenom Mar 24 '15 at 21:52
  • yes, the next generator will run because of `yield* next` – Eason Mar 25 '15 at 12:10
  • The next generator seems to be returning a 404. I guess because my route handler is the last in the stack. I am trying to save the results of the response to a local cache AFTER returning the result to the client. I can omit the yield *next and put my save code there- but that doesn';t seem smart. – akaphenom Mar 25 '15 at 14:32
0

If the generator solution doesn't work for you (e.g. you can't use generators, can't transition all of your app to using generators, you're running serverless, etc), you can call your task from a setImmediate instead, e.g.

app.use((ctx, next) => {
  await next();

  setImmediate(() => {
    void processData(ctx.request.body);
  });
});

async function processData() {
  // your async task here
}

setImmediate just puts your function directly onto the event queue, which node will then fire as soon as it can (in most cases, this is effectively immediate). This allows the main middleware function to return as normal, as you're not blocking / interrupting the control flow at all.

I'd imagine this probably works a bit better on serverless too, because they typically handle the timer functions correctly, although I haven't personally tested it.

Toastrackenigma
  • 7,604
  • 4
  • 45
  • 55
-3

you can run code in async task by use setTimeout, just like:

 exports.invoke = function*() {
  setTimeout(function(){
    co(function*(){
      yield doSomeTask();
    });
  },100);
  this.body = 'ok';
};
kinolee
  • 53
  • 7