I understand that when I set this.body and all middleware finished executing the response is sent.
What I want is to send the response at some point and then execute some remaining actions.
Simply setting this.body and then doing yield next doesn't seem to do it - it doesn't seem to automatically send the response after the current middleware. I might be wrong, please correct me if this is the case.
This is the simplified code of what I've tried:
var router = require('koa-router');
router.get('/test', store, process);
function *store(next) {
yield storeToDB(this.request.body);
this.body = 'OK';
// it seems like I need to do something here to send response, like res.send('OK') in express
try { yield next; }
catch(e) { console.log('processing error:', e); }
}
function *process() {
yield processData(this.request.body);
}
The response is sent after process, not after store as I need.
I understand that some separate process can read from DB on some notifications or schedule, but I wanted to keep it simple yet...
I've found this: http://stackoverflow.com/questions/26516082/running-code-after-the-response-has-been-sent-by-koa
It would be nice to have this.end() / this.response.end() that can be yielded.
try this:
app.use(function*(next){
yield next;
console.log('after response');
});
Yes, I've figured... As long as I don't yield anything after yield next, the response will be sent before the processing is completed.
What is a bit annoying is that I should use callbacks or promises in that part, I dont't seem to be able to have koa pushing my code forward with yields like it did before the response is sent if I do it this way.
Also, I am not sure why, it seems like unless I then promises the processing is not done... This I can't explain yet. Promise knows nothing about being then'ed, so how what it does can depend on whether I do it? Anyway, it's out of scope...
Probably I should use co in this part.
if i get your example correlty you would need to yield next; before yield processData(this.request.body);
You mean it should be
function *process(next) {
yield next();
yield processData(this.request.body);
}
?
yeah, but yield next, not next()
function *process(next) {
yield next;
yield processData(this.request.body);
}
I've tried, in this case the response is sent after processData yields, not after yield next, unless I am missing something. I will try to make some simple test.
well the promise of koa is that after yield next the response has been set, not that it has been fully sent. If your body however is a string, koa will call res.end(body) before yield next returns
Yes, and also content length needs to be set it seems... There are a couple workarounds all of them are not really koa-ish. So yield this.end(); would have been nice.
If you post a gist with a server file that can be run by anyone, I'm pretty sure we can solve the problem. Like this it's a little vague to tell the best solution.
server 1 (responds only after processing is done):
var app = require('koa')();
app
.use(respond)
.use(process);
function *respond(next) {
this.body = 'OK';
yield next;
}
function *process(next) {
yield next;
yield function (done) { setTimeout(done, 5000); };
console.log('processed');
}
app.listen(3000);
server 2 (responds before processing is done - that's what I need but with generators).
Only process is different:
// ...
function *process(next) {
yield next;
setTimeout(function() { console.log('processed'); }, 5000);
}
// ...
server 3 (same as 2 but with promise)
var app = require('koa')();
var Promise = require('bluebird');
app
.use(respond)
.use(process);
function *respond(next) {
this.body = 'OK';
yield next;
}
function *process(next) {
yield next;
Promise.delay(5000)
.then(function () {
console.log('processed');
});
}
app.listen(3000);
middleware order matters. try this:
var app = require('koa')();
app
.use(process)
.use(respond);
function *respond(next) {
this.body = 'OK';
}
function *process(next) {
yield next;
yield function (done) { setTimeout(done, 5000); };
console.log('processed');
}
app.listen(3000);
This way it also takes 5 seconds to respond, so it doesn't respond before processing is complete.
aaah now I see what you're trying to do. Yeah, if you want to do out of band processing you need to use co or callbacks. Please close this if this works for you.
Yes, I am using promises actually. As I wrote, it would be nice to be able to continue using koa flow after the response is sent. Something like yield this.end(). That's how it works in express. But closing for now, it's up to you if you add it.
+1 for this.end() feature. Now I'm developing something similar to bit.ly. I need to send redirect as soon as I can, and then store some info to database, etc. It would be nice to have this.end().. Now I have to do something like this.res.end().. But I still like Koa very much :)
So, basically, a way to short-circuit middlewares and send response (i.e. header and body) immediately? Then, how would downstream middlewares awaiting upstream to come back would know about it? Should setting ctx.body or a header throw an error if the response has been sent (or is being sent via a stream)?
throw an error if the response has been sent would be fair in my opinion . If I manually end() response earlier I should not set ctx.body anymore and if I do - it's my responsibility. I'm not saying you are wrong, I'm sure you guys (who build koa framework) are better at this (how framework should work). I'm just trying to figure out how to use it properly.
But let's say your task is to make bit.ly service, and you have to make 1 request to database to get destination URL, and five more requests to write some logs. How would you do:
1) Would send response after all 6 requests to database
2) Do some workaround to send response earlier.
Maybe it's specific case and there is no need to add this.end() feature to the framework. I'm just curious how would you do in this case.
Thanks!
What about this?
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);
}
If speed is an issue, I'd setup my koa app in one process and any other long data manipulation in another process. In pseudo code, this could be something like
var app = createKoaApp();
var pub = createExternalProcessPublisher();
app.use(function *(next) {
this.body = yield getRequestedInformation(this.req);
pub.send(collectedRequestInformation(this.req));
});
And, in some external process
var rcv = createReceiver();
rcv.on('message', function (data) {
/* handle data to log or whatever else with it */
});
This way, the HTTP server is dedicated to return whatever the user wants and nothing else. All other processing and error handling is done externally, independent on the user request.
Thanks @felixfbecker. Now I feel noob :) It was easier than I thought. And thanks @yanickrochon - another process for heavy data manipulation - good idea.
No prob. I will post that on stackoverflow too :)
Throwing error if response was/being sent is what express is doing, so it makes sense.
Another option would be to simply ignore and log a warning at some moment - also fine.
I agree, there are plenty of other options - using promises, callbacks, setting another service for processing, etc.
It still would be nice to have an explicit mechanism for early response. Once you are using generators it is a bit annoying to switch the syntax and to use promises after the request is sent. And spinning up the whole separate node app for processing is sometimes more than you want to do.
So, basically, a way to short-circuit middlewares and send response (i.e. header and body) immediately? Then, how would downstream middlewares awaiting upstream to come back would know about it?
Can be an additional property of ctx: e.g. ctx.sent or ctx.reponse.sent (boolean) or .phase (string).
Feature creep :)...
@epoberezkin do you have a usecase my answer doesn't solve?
Definitely your suggestion to create a separate micro service will solve any use case.
For the sake of keeping things simple though it can be better to do post-response processing in the same service. And once using generators I'd prefer not switching paradigms from generators to promises.
If you mean which cases require post-response processing there are plenty. I use it for handling incoming messages - 1) message is validated and stored, 2) response is sent, 3) message is processed.
Any scenario that involves expensive processing that does not affect the response (e.g. centralised logging, auditing, analytics, calculating rewards for transaction, etc.) is more efficient if the processing happens after the response is sent: 1) validate and store request or do some core action, 2) send response, 3) do processing or additional actions. It also increases probability of the response being sent even if some unhandled exception happens downstream.
It would extend what koa is good for.
There was no sperate "micro service" in my answer. You can do any async operation after setting the response body without yielding it and the response will be send before the async op starts.
The response will not start if you use generators (yields) for async operation. Please see above.
Of course it will :)
It doesn't matter if the async operation you do returns a thunk, a promise or takes a callback. Just don't yield it and koa will not wait for it to finish. If it's still not clear for you, post a code example and I show you.
@felixfbecker :+1:
app.use(async (ctx, next) => {
await next();
someSeriousBackgroundOperation(); // don't await, so the response will be sent immediately \o/
});
async function someSeriousBackgroundOperation() {
await verySlowDBQuery();
await someOutOfProcessImageProcessingWithCUDA();
...
}
koa has a clearly defined processing flow, where the topmost middleware sends the HTTP response (or at least i think this should be its primary purpose). If the response depends on the result of someSeriousBackgroundOperation(), then you should await on it, otherwise you should not care about it's result (don't await)
But how do I use yields inside someSeriousBackgroundOperation? What would call .next on it? If I don't yield it, koa would not .next it, I'll have to do it myself.
I guess it is fine, it just changes syntax in post-response compared with pre-response.
You have to use co or similar generator utility, if you are using generators (i'm assuming you are using koa@1, so no async functions).
app.use(function* (next) {
yield* next();
co(someSeriousBackgroundOperation);
});
function* someSeriousBackgroundOperation() {
yield verySlowDBQuery();
yield someOutOfProcessImageProcessingWithCUDA();
...
}
Yeah all koa does is call co behind the scenes, which is why it's being removed in koa2 (less magic).
I see. That's fine, thanks
But instead of @madbence's example I would suggest to do
const someSeriousBackgroundOperation = co.wrap(function* () {
yield verySlowDBQuery();
yield someOutOfProcessImageProcessingWithCUDA();
...
})
app.use(function* (next) {
yield* next();
someSeriousBackgroundOperation();
});
So once async functions ship in V8 you only have to replace co.wrap with async and yield with await.
+1
I tried @felixfbecker's solution, but yield* next() returns the error "next(...)[(intermediate value)] is not a function.
module.exports.foo = function *(next) { // used in koa-router config
...
yield* next();
someSeriousBackgroundOperation();
})
I am using koa 1.2.0 with koa-router.
I tried @felixfbecker's solution, but
yield* next()returns the error"next(...)[(intermediate value)] is not a function.module.exports.foo = function *(next) { // used in koa-router config ... yield* next(); someSeriousBackgroundOperation(); })I am using koa 1.2.0 with koa-router.
@firla yield* next() should be replaced by yield next()
Most helpful comment
But instead of @madbence's example I would suggest to do
So once async functions ship in V8 you only have to replace
co.wrapwithasyncandyieldwithawait.