Running code AFTER the response has been sent by Koa
Asked Answered
C

4

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'
Chevaldefrise answered 22/10, 2014 at 20:2 Comment(6)
How do you send the request? There are usually success callbacks for this kind of thingKalindi
Please show us your code. Usually, you'd just call response.end() to send/flush the response, and continue with your work.Cola
@Cola response.end() isn't defined: github.com/koajs/koa/blob/master/docs/api/response.md. I use koa-router. I'll add some code.Chevaldefrise
Thanks. But isn't that Coffeescript, not ES6?Cola
Coffeescript in ES6. I just forgot to yield. Will fix. Also, response.end() isnt supported by koa: github.com/koajs/koa/blob/master/docs/api/context.md#ctxresChevaldefrise
do a setImmediate(). if it "looks" synchronous in koa, it's going to block the request.Interdiction
V
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);
}
Viridescent answered 3/11, 2015 at 16:8 Comment(1)
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 madeIlyssa
M
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;
};
Markley answered 20/3, 2015 at 9:32 Comment(3)
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?Quillan
yes, the next generator will run because of yield* nextMarkley
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.Quillan
K
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.

Kazantzakis answered 17/7, 2023 at 7:42 Comment(0)
S
-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';
};
Stagestruck answered 20/12, 2016 at 8:15 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.