Why the code in Promise.then() not been called?
I'm writting a server with expressjs and bluebird promise.When I tried to promisify a function using Promise.promisify(), I found that the code I wrote in Promise.then() doesn't work.My codes are as follow
var Promise = require('bluebird');
var test = function(req) {
console.log('123');
};
var regist=Promise.promisify(test);
app.post('/test', function (req, res) {
reg.regist()
.then(function () {
console.log('456');
});
I post a request to '/test' and only saw 123 printed in the console.How could I make the codes in the Promise.then() work?
Promise.promisify creates a mechanism that automatically passes in a callback function as the last argument to your original function. Your function doesn't take a callback function and never calls one, so there's no way for the promise to resolve.
If your function is not asynchronous, there isn't much point in promisifying it, but here's how you could do so:
var test = function(req, callback) {
console.log('123');
callback();
};
var regist = Promise.promisify(test);
var request = { someProperty: 'some value'};
regist(request)
.then(function() {
console.log('456');
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.4.7/bluebird.min.js"></script>
Related
I am trying to use a promise to call a function getTweets.
Not using an AJAX call, but a simple promise 'call' from 1 javascript file to another.
The function works, but i keep getting 'undefined'.
I have read dozens of questions here on stackoverflow and have spent days
to understand promises, but still can't solve it.
var Twit = require('twit') // Imports the Twitter library
require('dotenv').config() // to get the environment vars into the app
// This is the function:
function getTweets (screen_name) {
let T = new Twit({ /* <twitter key and token here> */ });
T.get('statuses/user_timeline', { screen_name: screen_name, count: 3}, function (err, data, response) {
let myTweets = [];
for (let i in data) {
let text = data[i].text;
myTweets.push(text);
}
return myTweets;
})
}
module.exports.getTweets = getTweets;
And this is the promise that tries to get the tweets:
var promise = tweets.getTweets('dizid');
promise.then(
console.log(myTweets),
console.log(err))
// gives error: promise.then(
// ^
// TypeError: Cannot read property 'then' of undefined
Any help greatly appreciated.
Your problem is that you never return anything from your getTweets() function even though it needs to return a promise. The function calls T.get() and pass it a callback function. You return from this callback function but this doesn't do anything, it doesn't mean that this value gets returned from getTweets().
This is a pretty common mistake when it comes to working with asynchronous calls. What needs to be done is to make getTweets() return a promise that gets resolved when it should.
When working with asynchronous calls that don't implement the promise interface, you need to wrap this call with a new promise. Your getTweets() function should then look like this:
function getTweets (screen_name) {
let T = new Twit({ /* <twitter key and token here> */ });
return new Promise(function(resolve, reject) {
T.get('statuses/user_timeline', { screen_name: screen_name, count: 3}, function (err, data, response) {
if (err) {
reject(err); // Reject the promise since there was an error
} else {
let myTweets = [];
for (let i in data) {
let text = data[i].text;
myTweets.push(text);
}
resolve(myTweets); // Resolve the promise with the result
}
});
});
}
However, it seems the Twit API does support the promise interface, so instead of providing a callback function you can just use the promise created by T.get(). HMR's answer explains how to do this.
Another mistake you've made is with this code:
promise.then(
console.log(myTweets),
console.log(err))
The way you've written it, it reads "Run console.log(myTweets) and console.log(err), then invoke promise.then() with the result of the former as the first argument and the result of the latter as the second argument.
then() takes callback functions (which get invoked depending on the resolving/rejection of the promise) as arguments, so the code should look like this:
promise.then(
function(myTweets) {
console.log(myTweets);
},
function(err) {
console.log(err);
});
Async/await
If you're interested in taking things further, the modern approach to working with asynchronous code is async/await, which is syntactic sugar for promises that lets you write asynchronous code more similar to regular synchronous code.
A function marked as async will implicitly return a promise, but you write it as if you return a regular value. Using the await keyword inside an async function will implicitly wait for a promise to resolve and unwrap the resolved value. The main practical benefits of this is that you can use asynchronous calls in loops and handle errors with regular try-catch blocks. Your getTweets() function would look like this using async/await:
async function getTweets(screen_name) {
let T = new Twit({ /* <twitter key and token here> */ });
const data = await T.get('statuses/user_timeline', { screen_name: screen_name, count: 3});
// Let's also use map() instead of a for loop
let myTweets = data.map(function(item) { return item.text; });
return myTweets;
}
Since get seems to return a promise you don't need to use a callback. Get Tweets can look something like this:
// in getTweets
return T.get(
'statuses/user_timeline',
{ screen_name: screen_name, count: 3}
).then(
function (data) {
console.log("data:",JSON.stringify(data,undefined,2));
return data.map(item=>item.text);
}
)
// exports the function getTweets so that other modules can use it
module.exports.getTweets = getTweets;
If that didn't work please let us know what the output of the program is (update question).
You can call getTweets like so:
tweets.getTweets('dizid')
.then(
myTweets=>
console.log(myTweets),
err=>
console.log(err)
)
I think you forget add function like
promise.then(function(res){
//code
}
Your .then() should include a call back function.
promise.then( res => {
console.log(res);
});
edit: I'm using an ES6 syntax for arrow functions, in case you're new to that.
i am taking my first steps with node.js and i came across this issue with passing variable in asynchronous way. I have this piece of code im using to create Facebook user:
req.tmpPassport = {};
var fb = new fbgraph.Facebook(accessToken, 'v2.2');
function initUser() {
fb.me(function (err, me) {
req.tmpPassport.me = me;
console.log(req.tmpPassport.me) // works
});
}
console.log(req.tmpPassport.me) // not working -> undefined var
i tried to figure out why the second log isn't working and i ended up reading about synchronous and asynchronous functions, so in attempt to implement what i read i tried coming up with a solution using callbacks, but no success.
my last attempt was this:
req.tmpPassport = {};
var fb = new fbgraph.Facebook(accessToken, 'v2.2');
function initUser() {
fb.me(function (err, me) {
req.tmpPassport.me = me;
});
fb.my.events(function (err, events) {
//console.log(events);
req.tmpPassport.events = events;
});
fb.my.friends(function (err, result) {
req.tmpPassport.results = result;
});
}
function passUser(){
console.log(req.tmpPassport);
return req.tmpPassport;
}
cp.exec(initUser, passUser);
but its not working...
what i am actually trying to achieve its to render this object with my express router var which looks like this:
router.get('/welcome', securePages, function(req, res, next){
res.render('welcome', {title:'Welcome to aDating', user:req.tmpPassport});
})
but i cant figure out how to pass this object only after created...any help please?
A method of chaining function calls when certain async tasks are done is one way to deal with this.
Looking at the first snippet of code, it would be rewritten as follows:
req.tmpPassport = {};
var fb = new fbgraph.Facebook(accessToken, 'v2.2');
function initUser() {
fb.me(function (err, me) {
console.log(req.tmpPassport.me) // works
req.tmpPassport.me = me;
// triggers execution of the next step
post_populating_passport();
});
}
function post_populating_passport() {
// this function is only executed after the callback from the async call
console.log(req.tmpPassport.me);
}
Node.js is an asynchronous programming language, a fundamental concept in its core. You either need to use function callbacks (not a good practice) or available npms utilities for async flow.
In the file I would like to test, I have the following code:
var httpGet = Promise.promisify(require("request").get);
httpGet(endpoint, {
auth: {bearer: req.body.access_token},
json: true
})
.then(...)
Now, in my tests, I want to make sure that httpGet was called once, and make sure the parameters are valid. Before being promisified, my test looked like this:
beforeEach(function () {
request.get = sinon.stub()
.yields(null, null, {error: "test error", error_description: "fake google error."});
});
afterEach(function () {
expect(request.get).to.have.been.calledOnce();
var requestArgs = request.get.args[0];
var uri = requestArgs[0];
expect(uri).to.equal(endpoint);
//...
});
Unfortunately this no longer works when request.get is promisified. I tried stubbing request.getAsync instead (since bluebird appends "Async" to promisified functions), but that does not work either. Any ideas?
Promise.promisify doesn't modify the object, it simply takes a function and returns a new function, it is completely unaware that the function even belongs to "request".
"Async" suffixed methods are added to the object when using promisify All
Promise.promisifyAll(require("request"));
request.getAsync = sinon.stub()
.yields(null, null, {error: "test error", error_description: "fake google error."});
expect(request.getAsync).to.have.been.calledOnce();
Just for future reference I've solved this a bit differently, and I think a little cleaner. This is typescript, but basically the same thing.
fileBeingTested.ts
import * as Bluebird from 'bluebird';
import * as needsPromise from 'needs-promise';
const methodAsync = Bluebird.promisify(needsPromise.method);
export function whatever() {
methodAsync().then(...).catch(...);
}
test.spec.ts
import * as needsPromise from 'needs-promise';
import * as sinon form 'sinon';
const methodStub = sinon.stub(needsPromise, method);
import { whatever } from './fileBeingTested';
Then you use the methodStub to manage what calls happen. You can ignore that it's being promisified and just manage it's normal behavior. for example if you need it to error.
methodStub.callsFake((arg, callback) => {
callback({ error: 'Error' }, []);
});
The promisified version will throw the error and you'll get it in the catch.
Anyone coming across this. I have small utility func
function stubCBForPromisify(stub) {
let cbFn = function() {
let args = [...arguments];
args.shift();
return stub(...args);
};
return cbFn.bind(cbFn, () => ({}));
}
In test
var getStub = sinon.stub().yields(null, {error: "test error", error_description: "fake google error."})
sinon.stub(require("request"), 'get', stubCBForPromisify(getStub))
expect(getStub).to.have.been.calledOnce();
I was running into trouble testing this using tape and proxyquire. I'm not sure what pattern/framework people are using that allowed them to modify the required'd request object directly as shown in the accepted answer. In my case, in the file I want to test I require('jsonFile'), then call bluebird.promisifyAll(jsonFile). Under normal conditions this creates a readFileAsync method that I want to stub. However, if during testing I try to use proxyquire to pass in a stub, the call to promisifyAll overwrites my stub.
I was able to fix this by also stubbing promisifyAll to be a no-op. As shown this might be too coarse if you rely on some of the async methods to be created as-is.
core.js:
var jsonFile = require('jsonfile');
var Promise = require('bluebird');
Promise.promisifyAll(jsonFile);
exports.getFile = function(path) {
// I want to stub this method during tests. It is
// created by promisifyAll
return jsonFile.readFileAsync(path);
}
core-test.js:
var proxyquire = require('proxyquire');
var tape = require('tape');
var sinon = require('sinon');
require('sinon-as-promised');
tape('stub readFileAsync', function(t) {
var core = proxyquire('./core', {
'jsonfile': {
readFileAsync: sinon.stub().resolves({})
},
'bluebird': { promisifyAll: function() {} }
});
// Now core.getFile() will use my stubbed function, and it
// won't be overwritten by promisifyAll.
});
var Q = require('q');
Q.nfcall(client.get("time_clock", function (err, reply) {
var time = reply.toString();
return time;
})).then(function(time) {
client.get("time_predicted", function (err, replier) {
mom=replier.toString();
res.render('time', {watch: time, moment: mom});
})
}).fail(function(err){
console.log('Error.')})
.done();
};
This code fails. The code below works, without using promises (shown below). The code I want to fix is above.
client.get("time_clock", function (err, reply) {
time=reply.toString();
console.log("in here"+time); // Will print `OK`
client.get("time_predicted", function (err, replier) {
mom=replier.toString();
res.render('time', {watch: time, moment: mom});
});
});
What do I need to change in the first code example to make it work? (Note: it would be even better if I could call res.render at the very end, in the last or another 'then').
I won't directly answer to your question; but a simple solution is to use redis-then, a redis library for NodeJS that uses promises.
I want to call an asynchronous function inside a Meteor method and then return the result from that function to Meteor.call.
(How) is that possible?
Meteor.methods({
my_function: function(arg1, arg2) {
//Call other asynchronous function and return result or throw error
}
});
Use a Future to do so. Like this:
Meteor.methods({
my_function: function(arg1, arg2) {
// Set up a future
var fut = new Future();
// This should work for any async method
setTimeout(function() {
// Return the results
fut.ret(message + " (delayed for 3 seconds)");
}, 3 * 1000);
// Wait for async to finish before returning
// the result
return fut.wait();
}
});
Update:
To use Future starting from Meteor 0.5.1, you have to run the following code in your Meteor.startup method:
Meteor.startup(function () {
var require = __meteor_bootstrap__.require
Future = require('fibers/future');
// use Future here
});
Update 2:
To use Future starting from Meteor 0.6, you have to run the following code in your Meteor.startup method:
Meteor.startup(function () {
Future = Npm.require('fibers/future');
// use Future here
});
and then use the return method instead of the ret method:
Meteor.methods({
my_function: function(arg1, arg2) {
// Set up a future
var fut = new Future();
// This should work for any async method
setTimeout(function() {
// Return the results
fut['return'](message + " (delayed for 3 seconds)");
}, 3 * 1000);
// Wait for async to finish before returning
// the result
return fut.wait();
}
});
See this gist.
Recent versions of Meteor have provided the undocumented Meteor._wrapAsync function which turns a function with a standard (err, res) callback into a synchronous function, meaning that the current Fiber yields until the callback returns, and then uses Meteor.bindEnvironment to ensure that you retain the current Meteor environment variables (such as Meteor.userId()).
A simple use would be as the following:
asyncFunc = function(arg1, arg2, callback) {
// callback has the form function (err, res) {}
};
Meteor.methods({
"callFunc": function() {
syncFunc = Meteor._wrapAsync(asyncFunc);
res = syncFunc("foo", "bar"); // Errors will be thrown
}
});
You may also need to use function#bind to make sure that asyncFunc is called with the right context before wrapping it.
For more information see: https://www.eventedmind.com/tracks/feed-archive/meteor-meteor-wrapasync
Andrew Mao is right. Meteor now has Meteor.wrapAsync() for this kind of situation.
Here's the simplest way to do a charge via stripe and also pass a callback function:
var stripe = StripeAPI("key");
Meteor.methods({
yourMethod: function(callArg) {
var charge = Meteor.wrapAsync(stripe.charges.create, stripe.charges);
charge({
amount: amount,
currency: "usd",
//I passed the stripe token in callArg
card: callArg.stripeToken,
}, function(err, charge) {
if (err && err.type === 'StripeCardError') {
// The card has been declined
throw new Meteor.Error("stripe-charge-error", err.message);
}
//Insert your 'on success' code here
});
}
});
I found this post really helpful:
Meteor: Proper use of Meteor.wrapAsync on server
Another option is this package which achieves the similar goals.
meteor add meteorhacks:async
From the package README:
Async.wrap(function)
Wrap an asynchronous function and allow it to be run inside Meteor without callbacks.
//declare a simple async function
function delayedMessge(delay, message, callback) {
setTimeout(function() {
callback(null, message);
}, delay);
}
//wrapping
var wrappedDelayedMessage = Async.wrap(delayedMessge);
//usage
Meteor.methods({
'delayedEcho': function(message) {
var response = wrappedDelayedMessage(500, message);
return response;
}
});