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)
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;
}
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.
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.
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));
});
}
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.
Related
In one of my previous posts I realised I needed to promisify (turn a callback into a new promise) a npm module to make clean error handling. Problem is, the more I started to think about the organisation of promisifing a library the more it dawned on me that it could easily become a big fat mess. How should I access the promisified function, in what object? Could I maybe replace the original library functions with the new promisified functions? Should I just create a new object for the promisified functions? Example:
const mongodb = require("mongodb");
const { MongoClient } = mongodb;
//Promisifing MongoClient.connect(), and storing it into connectPr into the original library
mongodb.MongoClient.connectPr = function (url) {
const options = { useNewUrlParser: true, useUnifiedTopology: true };
const promise = new Promise(function (resolve, reject) {
MongoClient.connect(url, options, (error, client) => {
if (error) reject(error);
else resolve(client);
});
});
return promise;
};
//Exporting whole module with added functions
module.exports = mongodb;
What I am doing here is just modifing the original library and adding the new function connectPr into it. Then I export it and use it like this in another file:
const mongodb = require("./helperMongoDB");
const { MongoClient } = mongodb;
const connectionURL = "mongodb://127.0.0.1:27017";
const databaseName = "tasker";
//Using new connectPr function
(async function () {
const client = await MongoClient.connectPr(connectionURL);
const db = client.db(databaseName);
db.collection("tasks").insertOne(
{
description: "Clean the house",
completed: true,
},
(error, result) => {
if (error) {
console.error(error);
return;
}
console.log(result.ops);
}
);
})();
As u can see I can still use all the original properties from the module and I can also use my connectPr function. This works but its nothing pretty. I am sure that if I worked in a team of people that most of them would be pretty confused on why I am not requiring the library normally and why it is hidden somwhere in a helper file. What is the standard on doing this kind of thing? Is promisifing a callback function even a practise?
Be sure to first check that the asynchronous API does not already provide a promise interface. In your question you give the example of mongodb. This node API's asynchronous methods will return a promise when the callback argument is not provided.
For instance, take the connect method. The documentation says:
Returns:
Promise if no callback passed
So in an async function body you can write:
const client = await MongoClient.connect(url, options);
In case of the Mongoose library (built on top of the Node MongoDb driver API), when using query methods, you may need to chain an .exec() call, as is discussed in the answers to What does the exec function do?.
Any asynchronous library that wants to remain on track, will have a promise API included. If really this is not the case, look at Node's util.promisify
NodeJS gives me a warning
(node:32600) [DEP0013] DeprecationWarning: Calling an asynchronous
function without callback is deprecated.
when I run this ‘test’:
describe('whatever', () => {
it('test simple', async () => {
const dir = './build/fileTests';
if (fs.existsSync(dir)) {
console.log(`deleting ${dir}`);
await fs.rmdir(dir);
}
});
});
Well, I would almost agree, that using async-functions without a callback is bad (since only within the cb you can know for sure, it happened, go on, etc...
... if it wasn't for my intentional use of the ES7 async/await, because they make it synchronous, so I can work with whatever I get... (in this special case, I could evade to rmdirSync, but that's not my point...)
so my Question: How can I get rid of warnings like these?
– in a meaningful way, when working with async/await...
– dealing with the return value, as in const r = ... is not recognized as ‘callback handling’...
fs.rmdir does not return a promise object, that's why this code fails with swag. You have to promisify it , using a library or the node.js promisify method in util core module
Note: if you use this same approach for other async methods in the fs core module, it will fail
do this
const util = require("util");
const fs = require("fs");
const removeDir = util.promisify(fs.rmdir);
const rmDir = async () => {
try {
await removeDir("directory_name");
} catch(ex) {
console.error(ex)
}
}
Just make sure you promisify it
Edit: declared a variable to hold the value of uti.promisify(fs.rmdir) as suggested by #bergi
Edit: Add error handling with try .. catch block
As part of my learning of Node internals, I'm trying to add some basic functions to the Node response prototype without requiring some external libraries. It shouldn't be a difficult task, however, the response is never passed to the new functions and can never be retrieved through the this statement. This is an example to bind a template render function to the server response.
const http = require('http');
const newMethods = Object.create(http.ServerResponse.prototype);
newMethods.render = async (view, data) => {
const renderResult = await aTemplateLibray(view, data);
this.writeHead(200, {'Content-Type': 'text/html'});
this.end(renderResult);
}
// Other methods here...
// Then
Object.assign(http.ServerResponse.prototype, newMethods);
module.exports = http;
Once I use this http server, I can use the new render function, but the response is not passed to it, so an error message is thrown, something like this.writeHead is not a function.
I also tried with the Object.defineProperty method.
Object.defineProperty(http.ServerResponse.prototype, 'render',
{writable: true,
enumerable: true,
get: return this.socket.parser.incoming
});
I found some old library that returned that socket with the old __defineGetter__ method and I test it with the new form, but it doesn't work either.
The main issue is your use of the arrow function expression (=>), which has particular consequences with regards to what this points to inside the function (more on that here).
In your case, you want to use async function(...):
newMethods.render = async function(view, data) {
const renderResult = await aTemplateLibray(view, data);
this.writeHead(200, {'Content-Type': 'text/html'});
this.end(renderResult);
}
I am trying to use http.request on node using the koajs framework. Is there a way I can utilize it as show below?
var http = require('http');
var result = yield http.request(options);
Presumably the problem you're facing is that http.request takes a callback rather than returning a promise, so you can't yield it from koa. You need to wrap http.request in a function that returns a promise and hook the promise resolve into the callback, while also hooking the promise reject into the error handler.
function request(opts, body) {
return new Promise((resolve, reject) => {
body.pipe(http.request(opts, resolve))
.on('error', reject);
});
}
...later in your koa function...
var response = yield request(opts, body);
There are so many possible variations on this that I couldn't come close to listing them all, but that's the basic idea :)
I have a function that pulls out from database a random question from Questions collection.
Game_Questions.js - console.log below prints out correct value (string I need), so I thought that return will let yield give me back same value.
exports.random_Question = function *() {
yield Questions.findRandom().limit(1).exec(function(err,question){
console.log("rand q: " + question[0].text);
return question[0].text;
});
}
Game.js:
var Game_Questions = require('./backend/Game_Questions');
And here I want to access question[0].text value from random_Question function from code snippet above (Game_Questions.js). What I've tried so far:
var found_Question = Game_Questions.random_Question();
var found_Question = Game_Questions.random_Question().next().value;
Those two return [Object object] which after using JSON.stringify() shows that the object is:
{"value":{"emitter":{"domain":null,"_events":{}},"emitted":{},"ended":true},"done":false}
I also tried using co(function*()) but it also didn't let me take out the value. Please help how to access it?
The answer by #remus is a callback approach and Koa was designed explicitly to ditch callbacks. So while it's perfectly good code and would fit an Express application it is completely at odds with the design philosophy behind Koa.
From the looks of it you are using Mongoose which has supported promises for async operations since version 4.0 (which was released Apr 2015) which should allow a yield approach to be taken. Note I'm making an assumption you are working with Mongoose - I hope I'm not wrong!
Here is some nice documentation on how Mongoose would fit nicely with koa.
So first of all make sure you are using a version of Mongoose that supports using yield. If not you'll have to use the #remus approach or manually wrap each of your methods so they are yield compatible (i.e. wrapping with promises).
But if you are using a compatible version (4.0 and upwards) then your code would look something like the following:
exports.random_Question = function *() {
var result;
try {
result = yield Questions.findRandom().limit(1).exec();
} catch(e) {
console.log(e.stack);
throw e;
}
console.log("rand q: " + result[0].text);
return result[0].text;
}
Note that I'm assuming the result is an array based on the code you supplied.
The above example doesn't necessarily have to be a generator function. It could also be a normal function that returns a Promise. So alternatively something like this could also be done:
exports.random_Question = function() {
return Questions.findRandom()
.limit(1)
.exec()
.then(function() {
// I'm assuming mongoose assigns the value
// being resolved in exec() to 'this'
var question = this[0];
console.log("rand q: " + question.text);
return question.text;
}).catch(function(e) {
console.log(e.stack);
throw e;
});
}
So for the randomQuestion function all that is important is that it can be yielded by co which handles the Koa application flow control – check tj/co on GitHub for the different objects you can yield.
So finally getting back to the Koa Middleware we can yield either of the above code snippets in the exact same manner. So we'd do:
var koa = require("koa");
var app = module.exports = koa();
var Game_Questions = require('./backend/Game_Questions');
app.use(function*() {
var resultText;
try {
resultText = yield Game_Questions.random_Question();
} catch(e) {
this.throw(500);
}
this.body = resultText;
this.status = 200;
});
app.listen(3000);
Something else to note is that I'm a little unsure of the findRandom method in the mongoose query since I don't know if it plays nicely with the Promise features of mongoose. Personally I'd get a normal mongoose query working using yield before reintroducing findRandom just to make sure it's not causing an issue.
My answer is getting a bit long at this point so I'll leave it at that.
Your syntax is pretty strange, but not sure if that's specific to Koa or not?
Because Node.js is event based, use a callback instead:
exports.random_Question = function(callback) {
Questions.findRandom().limit(1).exec(function(err, question){
callback(err, question);
});
}
And use it:
var Game_Questions = require('./backend/Game_Questions');
Game_Questions.random_Question(function(err, question) {
console.log(question);
});
Of some concern as well is your question states you're trying to reference Game_Questions.randomQuestion() when your function is actually named random_Question.