Using Callbacks With nodejs in KOA
Asked Answered
V

5

7

Recently I work on a new project and this project use JavaScript callbacks in nodejs. Now we use KOA but the problem happens when we try to use ES6 Generators and callbacks.

//Calback function
function load(callback){
  result = null;
  //Do something with xmla4js and ajax
  callback(result);
  return result;
}

Now in KOA I need to call load and response json to client so i use this code below :

router= require('koa-router');
app = koa();
app.use(router(app));

app.get('load',loadjson);

function *loadJson(){
  var that = this;
  load(function(result){
    that.body = result;
  });
}

but i get this error :

_http_outgoing.js:331
throw new Error('Can\'t set headers after they are sent.');
      ^
Error: Can't set headers after they are sent.
at ServerResponse.OutgoingMessage.setHeader (_http_outgoing.js:331:11)
at Object.module.exports.set (G:\NAP\node_modules\koa\lib\response.js:396:16)
at Object.length (G:\NAP\node_modules\koa\lib\response.js:178:10)
at Object.body (G:\NAP\node_modules\koa\lib\response.js:149:19)
at Object.body (G:\NAP\node_modules\koa\node_modules\delegates\index.js:91:31)
at G:\NAP\Server\OlapServer\index.js:40:19
at G:\NAP\Server\OlapServer\OLAPSchemaProvider.js:1599:9
at _LoadCubes.xmlaRequest.success   (G:\NAP\Server\OlapServer\OLAPSchemaProvider.js:1107:13)
at Object.Xmla._requestSuccess (G:\NAP\node_modules\xmla4js\src\Xmla.js:2113:50)
at Object.ajaxOptions.complete (G:\NAP\node_modules\xmla4js\src\Xmla.js:2024:34)
Vaulting answered 27/2, 2014 at 5:28 Comment(2)
this error is because of multiple res.send(). Response is already sent and again you are trying to send .Xylophagous
in load function i need ajax because xmla4js use it. but i can log result in load only can't send to client. is this problem happen by ajax or xmla4js?Vaulting
S
15

Just to clarify things, let's write your callback as

//Calback function
function load(callback){
    setTimeout(function() {
        var result = JSON.stringify({ 'my': 'json'});
        callback(/* error: */ null, result);
    }, 500);
}

in Koa world, this is called a thunk, meaning that it is an asynchronous function that takes only one argument: a callback with the prototype (err, res). you can check https://github.com/visionmedia/node-thunkify for a better explanation.

now you have to write your middleware with

function *loadJson(){
  this.type = 'application/json';
  this.body = yield load;
}
Swept answered 27/2, 2014 at 8:4 Comment(0)
T
1

this is mainly because KOA is generator based, if your on the top of the middleware it does not support callbacks. so its not waiting for the function to finish. best solution would be to convert your function into a promise. promise works great with KOA.

Trunkfish answered 6/7, 2015 at 12:30 Comment(2)
can you provide an example of a promise in relation to the question?Hoffmann
you can use that example above. the chosen answer. or if you like you can use the co libraryTrunkfish
P
0

I had a very similar problem using braintree (regular callbacks) and koa. Based on your code, the only change I needed to do was with the load function and how it was called.

router = require('koa-router');
app = koa();
app.use(router(app));

app.get('/load',loadjson);

function *loadJson(){
  this.body = yield load;
}

// Callback function
function load(callback) {
  // Prepare some data with xmla4js and ajax
  whatever_inputs = {...};
  final_method(whatever_inputs, callback);
}

The explanation by Jerome and Evan above is absolutely correct, and thunkify looks like a suitable process for automatically doing it.

Pam answered 31/12, 2015 at 6:5 Comment(0)
C
0

While thunks were a nice idea, in my view a Promise is a better long-term approach. Many libraries are already moving to promises for async instead of the old node standard callback(err, data), and they're dead-simple to wrap around any async code to make a promise. Other devs will have experiences with Promises and naturally understand your code, while most would have to look up what a "thunk" is.

e.g. here I am wrapping the not-yet-promise-based jsdom up in a promise, so I can yield it in my koa generator.

const jsdom = require('node-jsdom');
const koa = require('koa');
const app = koa();
​
app.use(function *() {
  this.body = yield new Promise((resolve, reject) => jsdom.env({
    url: `http://example.org${this.url}`,
    done(errors, { document }) {
      if (errors) reject(errors.message);
      resolve(`<html>${document.body.outerHTML}</html>`);
    },
  }));
});
​
app.listen(2112);

Semantically, promises and generators go hand-in-hand to really clarify async code. A generator can be re-entered many times and yield several values, while a promise means "I promise I'll have some data for you later". Combined, you get one of the most useful things about Koa: the ability to yield both promises and synchronous values.

edit: here's your original example wrapped with a Promise to return:

const router = require('koa-router');
const { load } = require('some-other-lib');
const app = koa();
app.use(router(app));

app.get('load', loadjson);

function* loadJson() {
  this.body = yield new Promise(resolve => {
    load(result => resolve(result));
  });
}
Crude answered 15/6, 2016 at 0:1 Comment(0)
E
-1

To bypass Koa's built-in response handling, you may explicitly set this.respond = false;. Use this if you want to write to the raw res object instead of letting Koa handle the response for you.

Header is already written by built-in response handling before your callback is invoked.

Entrepreneur answered 22/9, 2014 at 10:53 Comment(2)
Please note that this.respond is not officially supported by Koa and make break Koa applications.Pawnshop
I don't really see this as an answer to the question. It's more appropriate as a comment elsewhere.Crude

© 2022 - 2024 — McMap. All rights reserved.