Why koa-router sends 404? - node.js

I'm using a koa-router, koa-views and sequelize. Data comes from a database, but the status = 404. What am I doing wrong?
router.get('/', function *() {
var ctx = this;
yield models.drivers.findAll({
where: {
userId: ctx.passport.user.id
}
}).then(function(drivers) {
ctx.render('driversSearch', {
drivers: drivers
});
});
});

Looks like you're not taking advantage of Koa's coroutine features. Your code can be rewritten like this:
router.get('/', function *() {
var drivers = yield models.drivers.findAll({
where: {
userId: this.passport.user.id
}
});
this.render('driversSearch', {
drivers: drivers
});
});
Koa uses the co library under the hood. If you yield a promise, the generator function will pause and then resume when the promise is fulfilled.

Related

Chaining async await calls in Node/Express with an external time limit

I'm building a Slackbot that makes a call to an Express app, which then needs to 1) fetch some other data from the Slack API, and 2) insert resulting data in my database. I think I have the flow right finally using async await, but the operation is timing out because the original call from the Slackbot needs to receive a response within some fixed time I can't control. It would be fine for my purposes to ping the bot with a response immediately, and then execute the rest of the logic asynchronously. But I'm wondering the best way to set this up.
My Express route looks like:
const express = require('express');
const router = express.Router();
const knex = require('../../db/knex.js');
const slack = require('../../services/slack_helpers');
// POST api/slack/foo
router.post('/foo', async (req, res) => {
let [body, images] = await slack.grab_context(req);
knex('texts')
.insert({ body: body,
image_ids: images })
.then(text => { res.send('worked!'); }) // This sends a response back to the original Slackbot call
.catch(err => { res.send(err); })
});
module.exports = router;
And then the slack_helpers module looks like:
const { WebClient } = require('#slack/web-api');
const Slack = new WebClient(process.env.SLACKBOT_TOKEN);
async function grab_context(req) {
try {
const context = await Slack.conversations.history({ // This is the part that takes too long
channel: req.body.channel_id,
latest: req.headers['X-Slack-Request-Timestamp'],
inclusive: true,
limit: 5
});
} catch (error) {
return [error.toString(), 'error'];
}
return await parse_context(context);
};
function parse_context(context) {
var body = [];
context.messages.forEach(message => {
body.push(message.text);
});
body = body.join(' \n');
return [body, ''];
}
module.exports = {
grab_context
};
I'm still getting my head around asynchronous programming, so I may be missing something obvious. I think basically something like res.send perhaps needs to come before the grab_context call? But again, not sure the best flow here.
Update
I've also tried this pattern in the API route, but still getting a timeout:
slack.grab_context(req).then((body, images) => {
knex ...
})
Your timeout may not be coming from where you think. From what I see, it is coming from grab_context. Consider the following simplified version of grab_context
async function grab_context_simple() {
try {
const context = { hello: 'world' }
} catch (error) {
return [error.toString(), 'error']
}
return context
}
grab_context_simple() /* => Promise {
<rejected> ReferenceError: context is not defined
...
} */
You are trying to return context outside of the try block where it was defined, so grab_context will reject with a ReferenceError. It's very likely that this error is being swallowed at the moment, so it would seem like it is timing out.
The fix is to move a single line in grab_context
async function grab_context(req) {
try {
const context = await Slack.conversations.history({
channel: req.body.channel_id,
latest: req.headers['X-Slack-Request-Timestamp'],
inclusive: true,
limit: 5
});
return await parse_context(context); // <- moved this
} catch (error) {
return [error.toString(), 'error'];
}
};
I'm wondering the best way to set this up.
You could add a higher level try/catch block to handle errors that arise from the /foo route. You could also improve readability by staying consistent between async/await and promise chains. Below is how you could use async/await with knex, as well as the aforementioned try/catch block
const express = require('express');
const router = express.Router();
const knex = require('../../db/knex.js');
const slack = require('../../services/slack_helpers');
const insertInto = table => payload => knex(table).insert(payload)
const onFooRequest = async (req, res) => {
try {
let [body, images] = await slack.grab_context(req);
const text = await insertInto('texts')({
body: body,
image_ids: images,
});
res.send('worked!');
} catch (err) {
res.send(err);
}
}
router.post('/foo', onFooRequest);
module.exports = router;

Async/Await Mongoose doesn't always run correctly

I'm using mongoose with an async/await function to query the DB. A few calls to the api doesn't return the necessary data. Below is the controller code
exports.GetAllUrls = async function(req, res, next){
try {
var urlsArray = [];
await Url.find({uid: req.params.uid}, function (err, urls) {
urls.forEach(function(url){
urlsArray.push(url);
console.log(url);
});
});
console.log("Await called");
return res.status(200).json({reply: urlsArray});
} catch(err) {
console.log(err);
}
}
There are a few times where the "Await called" is logged before the url data is logged.
Node Request Log:
Await called
GET /api/screens/GetAllUrls/0WyaS0ePeaS54zz9cAgCUnxoE1i1 200 31.348 ms - 12
{ _id: 5b0ad7effa8e80800153fa04,
url: 'https://yahoo.com',
timestamp: 2018-05-27T16:31:10.638Z,
uid: '0WyaS0ePeaS54zz9cAgCUnxoE1i1',
__v: 0 }
As seen in the logs, the function seems to proceed before the await function is called, but my understanding is that execution is paused until the await is completed and returned. Anyone have any ideas why this is happening?
I don't know the specifics on that library or method, but I can tell you why it's not working.
"await" will pause only when the right hand side of the statement returns a "Promise" object. In your code example, it seems that the function takes a callback. Callbacks, though asynchronous, are not promises. Perhaps you can check that library's API docs to see if it can return a Promise instead of taking the callback?
You are mixing callbacks with async-await. Don't do that. Please study how they work: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Your route handler should be:
exports.GetAllUrls = async function(req, res, next){
try {
const urlsArray = await Url.find({uid: req.params.uid}).exec()
return res.status(200).json({reply: urlsArray});
} catch(err) {
console.log(err);
}
}
.find() returns a Query object: http://mongoosejs.com/docs/api.html#find_find
.exec() returns a Promise: http://mongoosejs.com/docs/api.html#query_Query-exec

How can I stub a Promise such that my test can be run synchronously?

I am trying to unit test a module by stubbing one of its dependencies, in this case the UserManager
A simplified version of the module is as follows:
// CodeHandler
module.exports = function(UserManager) {
return {
oAuthCallback: function(req, res) {
var incomingCode = req.query.code;
var clientKey = req.query.key;
UserManager.saveCode(clientKey, incomingCode)
.then(function(){
res.redirect('https://test.tes');
}).catch(function(err){
res.redirect('back');
}
);
}
};
};
I'm stubbing the UserManager's saveCode function which returns a Promise such that it returns a resolved Promise, but when I assert that res.redirect has been called, alas at the time of the assertion res.redirect has not yet been called.
A simplified version of the unit test is:
// test
describe('CodeHandler', function() {
var req = {
query: {
code: 'test-code',
key: 'test-state'
}
};
var res = {
redirect: function() {}
};
var expectedUrl = 'https://test.tes';
var ch;
beforeEach(function() {
sinon.stub(UserManager, 'saveCode').returns(
new RSVP.Promise(function(resolve, reject){
resolve();
})
);
sinon.stub(res, 'redirect');
ch = CodeHandler(UserManager);
});
afterEach(function() {
UserManager.saveCode.restore();
res.redirect.restore();
});
it('redirects to the expected URL', function(){
ch.oAuthCallback(req, res);
assert(res.redirect.calledWith(expectedUrl));
})
});
How can I properly stub the promise such that the method under test behaves synchronously?
I've worked out a solution using sinon-stub-promise.
describe('CodeHandler', function() {
var req = {
query: {
code: 'test-code',
key: 'test-state'
}
};
var ch;
var promise;
var res = {
redirect: function() {}
};
beforeEach(function() {
promise = sinon.stub(UserManager, 'saveCode').returnsPromise();
ch = CodeHandler(UserManager);
sinon.stub(res, 'redirect');
});
afterEach(function() {
UserManager.saveCode.restore();
res.redirect.restore();
});
describe('can save code', function() {
var expectedUrl = 'https://test.tes';
beforeEach(function() {
promise.resolves();
});
it('redirects to the expected URL', function(){
ch.oAuthCallback(req, res);
assert(res.redirect.calledWith(expectedUrl));
});
});
describe('can not save code', function() {
var expectedUrl = 'back';
beforeEach(function() {
promise.rejects();
});
it('redirects to the expected URL', function(){
ch.oAuthCallback(req, res);
assert(res.redirect.calledWith(expectedUrl));
})
})
});
This works perfectly.
Well, the easiest thing would be not to stub it to run synchronously at all since that might change execution order and use Mocha's built in promises support (or jasmine-as-promised if using jasmine).
The reason is there can be cases like:
somePromise.then(function(){
doB();
});
doA();
If you cause promises to resolve synchronously the execution order - and thus output of the program changes, making the test worthless.
On the contrary, you can use the test syntax:
describe("the test", () => { // use arrow functions, node has them and they're short
it("does something", () => {
return methodThatReturnsPromise().then(x => {
// assert things about x, throws will be rejections here
// which will cause a test failure, so can use `assert`
});
});
});
You can use the even lighter arrow syntax for single lines which makes the test even less verbose:
describe("the test", () => { // use arrow functions, node has them and they're short
it("does something", () =>
methodThatReturnsPromise().then(x => {
// assert things about x, throws will be rejections here
// which will cause a test failure, so can use `assert`
});
);
});
In RSVP, you can't set the scheduler as far as I know so it's quite impossible to test things synchronously anyway, other libraries like bluebird let you do it at your own risk, but even in libraries that let you do it it's probably not the best idea.

Async call in node.js vs. mongoose

I have a node.js app that uses mongoose to connect to
a mongodb; i need to select all the documents inserted and i
i've problems with async stuff.
I've made a model with the following function:
exports.listItems=function() {
Ticket.find({}, function(err,tkts) {
console.log(tkts);
return tkts;
});
}
I correctly see the value of "tkts", but when i call it from:
exports.list = function(req,res) {
var items=db.listItems();
console.log("Items:"+items);
res.render('list', { title: title, items:items });
}
defined in app.js as:
app.get('/list', routes.list);
items is undefined (i think because of non-async definition of db.list()).
What am i doing wrong and how can it be corrected?
You need to use callbacks more appropriately.
A more traditional listItems function would be
exports.listItems = function(done) {
Ticket.find({}, done);
}
Then, in list, you could do:
exports.list = function(req,res) {
db.listItems(function(err,items){
console.log("Items:"+items);
res.render('list', { title: title, items:items });
});
}
Because of the asynchronous nature of Node.JS, you should always pass (and expect) a callback in your functions. So that you can defer execution if something asynchronous is executed.
Also: be sure to check out async, its an insanely good and easy-to-use library, that will simplify complex async scenarios in a breeze.

How to use mongoose Promise - mongo

Can someone give me an example on how to use a Promise with mongoose. Here is what I have, but its not working as expected:
app.use(function (req, res, next) {
res.local('myStuff', myLib.process(req.path, something));
console.log(res.local('myStuff'));
next();
});
and then in myLib, I would have something like this:
exports.process = function ( r, callback ) {
var promise = new mongoose.Promise;
if(callback) promise.addBack(callback);
Content.find( {route : r }, function (err, docs) {
promise.resolve.bind(promise)(err, docs);
});
return promise;
};
At some point I am expecting my data to be present, but how can I access it, or get at it?
In the current version of Mongoose, the exec() method returns a Promise, so you can do the following:
exports.process = function(r) {
return Content.find({route: r}).exec();
}
Then, when you would like to get the data, you should make it async:
app.use(function(req, res, next) {
res.local('myStuff', myLib.process(req.path));
res.local('myStuff')
.then(function(doc) { // <- this is the Promise interface.
console.log(doc);
next();
}, function(err) {
// handle error here.
});
});
For more information about promises, there's a wonderful article that I recently read:
http://spion.github.io/posts/why-i-am-switching-to-promises.html
Mongoose already uses promises, when you call exec() on a query.
var promise = Content.find( {route : r }).exec();
Mongoose 4.0 Release Notes
Mongoose 4.0 brings some exciting new functionality: schema validation
in the browser, query middleware, validation on update, and promises
for async operations.
With mongoose#4.1 you can use any promises that you want
var mongoose = require('mongoose');
mongoose.Promise = require('bluebird');
Another example with polyfilling global.Promise
require('es6-promise').polyfill();
var mongoose = require('mongoose');
So, you can just do later
Content
.find({route : r})
.then(function(docs) {}, function(err) {});
Or
Content
.find({route : r})
.then(function(docs) {})
.catch(function(err) {});
P.S. Mongoose 5.0
Mongoose 5.0 will use native promises by default if available,
otherwise no promises. You will still be able to set a custom promises
library using mongoose.Promise = require('bluebird');, however,
mpromise will not be supported.
I believe you're looking for
exports.process = function ( r, callback ) {
var promise = new mongoose.Promise;
if(callback) promise.addBack(callback);
Content.find( {route : r }, function (err, docs) {
if(err) {
promise.error(err);
return;
}
promise.complete(docs);
});
return promise;
};
On this page:http://mongoosejs.com/docs/promises.html
The title is Plugging in your own Promises Library
Use the bluebird Promise library like this:
var Promise = require('bluebird');
var mongoose = require('mongoose');
var mongoose = Promise.promisifyAll(mongoose);
User.findAsync({}).then(function(users){
console.log(users)
})
This is thenable, such as:
User.findAsync({}).then(function(users){
console.log(users)
}).then(function(){
// more async stuff
})

Resources