How do I wrap a Node.js callback using a Promise in Bluebird? This is what I came up with, but wanted to know if there is a better way:
return new Promise(function(onFulfilled, onRejected) {
nodeCall(function(err, res) {
if (err) {
onRejected(err);
}
onFulfilled(res);
});
});
Is there a cleaner way to do this if only an error needs to be returned back?
Edit
I tried to use Promise.promisifyAll(), but the result is not being propagated to the then clause. My specific example is shown below. I am using two libraries: a) sequelize, which returns promises, b) supertest (used for testing http requests), which uses node style callbacks. Here's the code without using promisifyAll. It calls sequelize to initialize the database and then makes an HTTP request to create the order. Bosth console.log statements are printed correctly:
var request = require('supertest');
describe('Test', function() {
before(function(done) {
// Sync the database
sequelize.sync(
).then(function() {
console.log('Create an order');
request(app)
.post('/orders')
.send({
customer: 'John Smith'
})
.end(function(err, res) {
console.log('order:', res.body);
done();
});
});
});
...
});
Now I try to use promisifyAll, so that I can chain the calls with then:
var request = require('supertest');
Promise.promisifyAll(request.prototype);
describe('Test', function() {
before(function(done) {
// Sync the database
sequelize.sync(
).then(function() {
console.log('Create an order');
request(app)
.post('/orders')
.send({
customer: 'John Smith'
})
.end();
}).then(function(res) {
console.log('order:', res.body);
done();
});
});
...
});
When I get to the second console.log the res argument is undefined.
Create an order
Possibly unhandled TypeError: Cannot read property 'body' of undefined
What am I doing wrong?
You are not calling the promise returning version and not returning it either.
Try this:
// Add a return statement so the promise is chained
return request(app)
.post('/orders')
.send({
customer: 'John Smith'
})
// Call the promise returning version of .end()
.endAsync();
Related
I am working on a Nuxt server side rendered application with the express framework. For authentication I am using the openid-client package. Now I want to store my retrieved token in the express session but the request model (req) is always undefined in the callback promise. To do this I want to use req.session.token = tokenSet.access_token. I am a Newbie in JavaScript so I think I am missing something obvious.
I tried various options on how to pass variables into a JavaScript promise but all of these required that you define the Promise by yourself which is not my case. I also tried waiting on the promise and using it outside of the callback promise but had no success either.
router.get('/api/oidc-callback', (req, res, params) => {
Issuer.discover('http://localhost:5000') // => Promise
.then(function(identityIssuer) {
const client = new identityIssuer.Client({
...
})
// HERE IT IS DEFINED
console.log(req)
client
.callback('http://localhost:3000/api/oidc-callback', req.query, {
code_verifier
})
// => Promise
.then(function(tokenSet) {
// HERE IT IS UNDEFINED
console.log(req)
req.session.token = tokenSet.access_token
}, req)
.catch(error => {
console.log(error)
})
//Also tried using outside
res.redirect('/oidc-callback')
})
})
Thanks in advance for your help!
You have two nested asynchronous operations (shown simplified here) and then you try to do something last in the first .then() handler:
Issuer.discover().then(function() {
client.callback().then(function() {
// ...
});
res.redirect('/oidc-callback');
});
This causes res.redirect() to get called before client.callback() is done as there is nothing in your code that make it wait for the completion of client.callback(). That sends the response and triggers a redirect before you modify the session which is not what you want to do. You can fix that in one of two ways:
1) Put the res.redirect() inside the inner .then() like this:
Issuer.discover().then(function() {
client.callback().then(function() {
// ...
res.redirect('/oidc-callback');
});
});
2) Add a return before client.callback() so that it will chain the inner promise to the outer one. Then, the outer one won't finish until the inner one is done and you can add another .then() handler to put the res.redirect() into:
Issuer.discover().then(function() {
return client.callback().then(function() {
// ...
});
}).then(function() {
// gets called when both asynchronous operations are done
res.redirect('/oidc-callback');
});
I'd recommend option #2 because it makes error handling simpler as you can do all your error handling in one place at the top level. Putting all that together, you'd end up with this:
router.get('/api/oidc-callback', (req, res, params) => {
Issuer.discover('http://localhost:5000').then(function(identityIssuer) {
const client = new identityIssuer.Client({
...
})
return client.callback('http://localhost:3000/api/oidc-callback', req.query, {
code_verifier
}).then(function(tokenSet) {
console.log(req);
req.session.token = tokenSet.access_token
}, req);
}).then(() => {
res.redirect('/oidc-callback');
}).catch(err => {
console.log(err);
res.sendStatus(500);
});
});
Note, I also added proper error handling at the end. This makes sure that no response is sent until both your async operations are done and if either one fails, it sends a proper error response.
I believe the reason why req is undefined because the promise was resolved after the execution of the middleware (res, req, next) =>{...}. Try returning the top level promise i.e. (res, req, next) =>{ return Issuer.discover(...)}, also add a return statement before client.callback(...).
router.get('/api/oidc-callback', (req, res, params) => {
return Issuer.discover('http://localhost:5000') // <-- added return here
.then(function(identityIssuer) {
const client = new identityIssuer.Client({
...
})
// HERE IT IS DEFINED
console.log(req)
return client // <-- added return here
.callback('http://localhost:3000/api/oidc-callback', req.query, {
code_verifier
})
// => Promise
.then(function(tokenSet) {
// HERE IT IS UNDEFINED
console.log(req)
req.session.token = tokenSet.access_token
res.redirect('/oidc-callback')
}) // removed , req here, it is not needed
.catch(error => {
console.log(error)
})
})
})
By adding the return statement, it tells express you are running an async function, thus express it going to wait until your middleware is resolved before moving on to the next middleware.
When i ran npm test i got a 'TypeError [ERR_HTTP_INVALID_HEADER_VALUE]: Invalid value "undefined" for header "x-access-token"' error. Seems like mocha moves on to the second test before getting the token. I tried adding a delay with the setTimeOut method but i still got the above error.
// creates valid-user object
const validUser = {
username: 'Rigatoni',
email: 'yahoo.com',
password: 'qwerty1234567',
};
describe('Post Tests', () => {
// login and get token...
let token;
before((done) => {
request(app)
.post('/api/v1/auth/login')
.send(validUser)
.end((err, res) => {
// eslint-disable-next-line prefer-destructuring
token = res.body.token;
console.log('token', token);
expect(res.status).to.equal(200);
});
// console.log('token test');
done();
});
describe('GET all posts', () => {
it('should return all posts', (done) => {
request(app)
.get('/api/v1/posts')
.set('x-access-token', token)
.end((err, res) => {
expect(res.body.success).to.equal(true);
});
done();
});
});
});
Your tests are almost right!
The done callback is provided to let Mocha know when it's OK to move on. However, you are calling done() in your tests right after calling the asynchronous request method; Mocha thinks the test is done before you even make the request.
Move your done() call for each test into a callback function (for example, on the line right after your expect()), so that it isn't executed until after the request completes. Then Mocha will wait until the test is over before moving on.
Example:
request(app)
.get('/api/v1/posts')
.set('x-access-token', token)
.end((err, res) => {
expect(res.body.success).to.equal(true);
done();
});
Using sequelize.js in a nodejs app, and I have a promise.all that takes two promises (a user query, and a color query):
router.get(`/someEndPoint`, (req, res) => {
let userAccount = user.findOne({
where: {
id: //some ID
}
});
let colorStuff = color.findOne({
where: {
colorName: //some color
}
})
Promise.all([userAccount , colorStuff ]).then(([result1, result2]) => {
//do stuff, such as:
res.send('success');
}).catch(err => {
console.log(err)
});
});
At the part that says //do stuff, my console keeps giving me this warning:
a promise was created in a handler at... but was not returned from it,
see (URL that I can't post) at Function.Promise.attempt.Promise.try
I'm not sure how to resolve this. I thought after the .then that the promises are resolved?
Hard to tell without other context, but perhaps you need to return the Promise.all
return Promise.all([user, color])...
From the bluebird docs here: https://github.com/petkaantonov/bluebird/blob/master/docs/docs/warning-explanations.md#warning-a-promise-was-created-in-a-handler-but-was-not-returned-from-it
if there are any other promises created in the // do stuff area, be sure to return those as well.
I'm trying to test 40+ API endpoints using Mocha. I would like to perform a few subtests as a part of a single server call.
For example, I would like to test if it('returns valid JSON... and it('returns a valid status code..., etc.
configs.forEach(function(config) {
describe(config.endpoint, () => {
it('...', function(done) {
server
.post(config.endpoint)
.send({})
.expect('Content-type', /json/)
.expect(200)
.end(function(err, res) {
//it('has a proper status code', () => {
expect(res.status).toEqual(200);
//})
//it('does not have an error object', () => {
expect(res.body.hasOwnProperty('error')).toEqual(false);
//})
done();
})
})
})
})
The problem is that I cannot nest it statements, but I am relying on the callback, via done() to dictate when the response has been received, so I have to wrap the call in an it statement...
Because some of these requests take half of a second to resolve, and there are 40+ of them, I don't want to create separate tests for these. Creating separate tests would also duplicate the config.endpoint, and I'd like to see if the tests are passing for each endpoint all in one place.
How can I create multiple tests for a single server call?
Here's how I accomplished this, using mocha, chai, and supertest (API requests):
import { expect } from "chai"
const supertest = require("supertest");
const BASE_URL = process.env.API_BASE_URL || "https://my.api.com/";
let api = supertest(BASE_URL);
describe("Here is a set of tests that wait for an API response before running.", function() {
//Define error & response in the 'describe' scope.
let error, response;
//Async stuff happens in the before statement.
before(function(done) {
api.get("/dishes").end(function(err, resp) {
error = err, response = resp;
done();
});
});
it("should return a success message", function() {
console.log("I have access to the response & error objects here!", response, error);
expect(response.statusCode).to.equal(200);
});
it("should return an array of foos", function() {
expect(response.body.data.foo).to.be.an("array");
});
});
configs.forEach(function(config) {
describe(config.endpoint, () => {
var response;
it('...', function(done) {
server
.post(config.endpoint)
.send({})
.expect('Content-type', /json/)
.expect(200)
.end(function(err, res) {
response=res;
done();
})
});
it('has a proper status code', () => {
expect(response.status).toEqual(200);
})
it('does not have an error object', () => {
expect(response.body.hasOwnProperty('error')).toEqual(false);
})
})
})
What about this ?
I am not sure about nesting of test cases but it will work for u.
I have several integration tests in my app:
it('creating vehicle', function (done) {
createVehicle()
.then(() => {
done();
})
.catch((err) => {
done(err);
});
});
createVehicle makes post request and returns promise:
return request.json('post', '/api/vehicle/')
.send(obj)
.expect(200)
.then((res) => {
expect(res.body).toExist("Body is empty");
expect(res.body.id).toExist("Id is empty");
return res.body;
});
Now all works fine, but if I rewrite first snippet in the following way:
it('creating vehicle', function (done) {
createVehicle()
.then(done) //*
.catch(done); //*
});
I get error from Mocha
done() invoked with non-Error
I understand why. The createVehicle return res.body and it's passed to then callback, in result done run as done(arg) and I get the error, because mocha done callback has to be called without arg when there is no error and with argument when there is error.
Is it possible to use this variant:
.then(done)
.catch(done);
How to achieve this?
Of course, I can delete return statement, but createVehicle is used in several places and I need returned value:
it('creating vehicle with media', function (done) {
createVehicle()
.then(createMedia) //createMedia will get returned value
//....
});
The easiest solution would be to just return the promise instead of having to deal with callbacks, because Mocha supports promises out-of-the-box:
it('creating vehicle', function() {
return createVehicle();
});