i'm new with Node.Js.
I want to make a function call in another function and when everything is finished, print a file.
But the functions are asynchronous so when the first ends node print the file without waiting the second one..
I tried the waterfall method of Async library but is not the waterfall that i need..
Async is a big library with a lots of different patterns, maybe one of these are the solution
This is the code:
request(url, function (error, response, html) {
var json_list = [];
if (!error) {
var $ = cheerio.load(html);
$('.fixed-recipe-card').each(function () {
var json = {title: "", description: "", rating: "", ingredients: [], url: ""};
json.title = $(this).children().last().children().first().text().trim();
json.description = $(this).children().last().children().first().next().children().last().text().trim();
json.rating = $(this).children().last().children().first().next().children().first().children().first().attr('data-ratingstars');
json.url = $(this).children().last().children().first().next().attr('href');
request(json.url, function (error, response, html) {
var $ = cheerio.load(html);
$('.checkList__line').filter(function () {
var ingredient = $(this).children().first().children().last().text().trim();
if (ingredient !== "Add all ingredients to list") {
console.log(ingredient);
json.ingredients.push(ingredient);
}
});
});
json_list.push(json);
});
}
fs.writeFile('output.json', JSON.stringify(json_list, null, 4), function (err) {
console.log('success');
})
res.send(JSON.stringify(json_list));
});
You could use Promises:
let promises = [];
$('.fixed-recipe-card').each(function () {
var json = {title: "", description: "", rating: "", ingredients: [], url: ""};
json.title = $(this).children().last().children().first().text().trim();
json.description = $(this).children().last().children().first().next().children().last().text().trim();
json.rating = $(this).children().last().children().first().next().children().first().children().first().attr('data-ratingstars');
json.url = $(this).children().last().children().first().next().attr('href');
let p = new Promise(function(resolve, reject) {
request(json.url, function (error, response, html) {
if(error) { reject(error); } // reject promise on error
var $ = cheerio.load(html);
$('.checkList__line').filter(function () {
var ingredient = $(this).children().first().children().last().text().trim();
if (ingredient !== "Add all ingredients to list") {
console.log(ingredient);
json.ingredients.push(ingredient);
}
});
resolve(response); // promise success
});
});
promises.push(p);
});
Promise.all(promises).then(function(values) {
console.log(values);
fs.writeFile('output.json', JSON.stringify(json_list, null, 4), function (err) {
console.log('success');
})
...
});
...
With Promise.all you can execute something when all async calls (built as promises) have been resolved
Check this link: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
Hope this helps
Related
I am using async for each to achieve some task the problem i am facing is that the final call back never executes
Scenario : i have list of contacts and want to send message to all contacts in parallel and when message is send want to store the response in array and than want to perform some action on final call back
sms.js
function SmsService() {}
SmsService.prototype.sendSms = function(value, callback) {
client.messages
.create({
body: value.body,
from: value.from,
to: value.to
})
.then(message => {
console.log('meesage going', message.sid);
callback(null,message.sid)
})
.catch(e => {
callback(null,'not send')
})
}
module.exports = SmsService;
sender.js
var SmsService = require(path.resolve(__dirname, './sms'));
var smsService = new SmsService();
var data = [{body:'1232324',from:'+12323123',to:'+12312323'},
{body:'112123234',from:'+123123123',to:'+123213123'}, {body:'12sadasdasd34',from:'+112123123',to:'+1223213123'}]
async.forEachOf(data, function (value, i, cb) {
console.log('started',i)
smsService.sendSms(value, function(error, result) {
console.log('sending',i,value.to)//only get result for first item
results.push(result)
cb()
})
}, function (err) {
if (err) console.error(err.message);
console.log('all done')//never executes
console.log(results);//never executes
});
If I move the async part to SMS service it works fine but I want to keep separate the SMS service
You can try something like this.
sms.js
function SmsService() {}
SmsService.prototype.sendSms = function(value, callback) {
return new Promise((resolve, reject) => {
// Do async job
client.messages
.create({
body: value.body,
from: value.from,
to: value.to
})
.then(message => {
console.log('meesage going', message.sid);
resolve(callback(null,message.sid))
})
.catch(e => {
reject(callback(null,'not send'))
})
})
}
module.exports = SmsService;
sender.js
var SmsService = require(path.resolve(__dirname, './sms'));
var smsService = new SmsService();
var data = [{body:'1232324',from:'+12323123',to:'+12312323'},
{body:'112123234',from:'+123123123',to:'+123213123'},
{body:'12sadasdasd34',from:'+112123123',to:'+1223213123'}];
var promises = [];
data.forEach(async function (obj, index) {
console.log('started',index)
promises.push(await smsService.sendSms(obj, function(error, result) {
console.log('sending',i,value.to)//only get result for first item
results.push(result)
}));
});
Promise.all(promises).then(function () {
console.log("Messages sent")
}).catch(function (err) {
console.log("Messages not sent")
})
My async function enters then before request is completed. Shouldn't Then part of the code executes only after the async function is completed? How to make the function call only when all the function has finished executing?
app.js
var request_test = require('./request_test');
baseUrl = "https://github.com";
promiseFunction().then((result)=>{
console.log("complete")
});
var promiseFunction = async function promiseFunction() {
request_test.request_test(baseUrl);
}
request_test.js
var request = require('request');
var cheerio = require('cheerio');
var request_test = function check(baseUrl) {
console.log("baseUrl:" + baseUrl)
var options = {
url: baseUrl
};
request(options, function (error, response, html) {
if (!error) {
console.log("no error");
}else{
console.log("else js");
console.log(error);
}
});
}
module.exports = {
request_test: request_test
};
In order to use then() you need to return a promise. So here is an example of the good old style promise chain, simply return promise from request_test and once you resolve or reject it, then() will be called:
promiseFunction().then((result) => {
console.log("complete");
});
function promiseFunction() {
return request_test();
}
function request_test() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log("no error");
resolve();
}, 1000);
});
}
Or maybe use the modern approach - async method to await call function that returns promise.
promiseFunction();
async function promiseFunction() {
await request_test();
console.log('complete');
}
function request_test() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log("no error");
resolve();
}, 1000);
});
}
Your issue is coming from var request_test = function check(baseUrl) { ... inside this function you are not returning promise, you are even returning nothing :)
if you are using async I would go ahead and use the await/async syntax. Also the package request does not return a promise, you have an alternative with request-promise-native. The promise should be the return value of your helper function. It could look like this:
var request_test = require('./request_test');
var baseUrl = "https://github.com";
var promiseFunction = async function () {
var result = await request_test.request_test(baseUrl);
console.log("complete");
}
promiseFunction();
and the module:
var request = require('request-promise-native');
var cheerio = require('cheerio');
var request_test = function check(baseUrl) {
console.log("baseUrl:" + baseUrl)
var options = {
url: baseUrl
};
return request(options).then(function (error, response, html) {
if (!error) {
console.log("no error");
} else{
console.log("else js");
console.log(error);
}
});
}
module.exports = {
request_test: request_test
};
I have the following two snippets:
1.this is the unit I would like to run:
health.checkHealth = function (req, res) {
async.parallel({
cassandra: function (callback) {
cassandraRepository.ping(callback);
},
kafka: function (callback) {
kafkaHelper.ping(callback);
},
vault: function (callback) {
vaultClient.health()
.then((isOk)=>{
callback(null, isOk)})
.catch(()=>
callback(null, false));
}
}, function (err, result) {
var response = {};
if (result.cassandra) {
response.cassandra = "OK";
} else {
response.cassandra = "Failed";
res.status(500);
}
if (result.kafka) {
response.kafka = "OK";
} else {
response.kafka = "Failed";
res.status(500);
}
if (result.vault) {
response.vault = "OK";
} else {
response.vault = "Failed";
res.status(500);
}
res.send(response);
})
}
2.this is the test to check the unit:
describe('vault is avaliable', function () {
beforeEach(sinon.test(function () {
sinon.stub(vaultClient, "health").resolves(true);
}));
it('should return 200', sinon.test(function (done) {
var response = {
vault: "OK"
};
var req = {};
var res = {};
var spySend = res.send = sinon.spy();
var spyStatus = res.status = sinon.spy();
health.checkHealth(req, res);
expect(spySend.calledOnce).to.equal(true);
expect(spySend.calledWith(response));
expect(spyStatus.calledOnce).to.equal(false);
}));
});
My problem is that when I call checkHealth it proceeds to the next line (expect(spySend.calledOnce).to.equal(true);) without waiting for the vaultClient's promise to complete.
What do I need to do to make the expects run only after the 'checkHealth' was run.
You do not need to use the async - you can use promises directly in your code with Promise.all as others have suggested.
The code here is using promisifyAll from Bluebird, but you can also convert the APIs to use promises yourself.
//.props is bluebird, you can `.all` with native promises too
Promise.props({
cassandra: cassandraRepository.pingAsync(); // created by promisifyAll
kafka: kafkaHelper.pingAsync(),
vault: vaultClient.health()
}).then(results => {
// access results.cassandra, results.kafka and results.vaule here
});
The code I wrote so far is as below.
I don't need the whole response but just part of it.
var request = require('request');
var async = require('async');
var asyncTasks = [];
var install;
async.series([
function (callback) {
setTimeout(function () {
request('URL', function (error, response, body) {
if (!error && response.statusCode == 200) {
console.log(body); // Show the HTML for the Google homepage.
}
});
}, 5000);
},
function (callback) {
setTimeout(function () {
request('URL', function (error, response, body) {
if (!error && response.statusCode == 200) {
console.log(body); // Show the HTML for the Google homepage.
}
});
}, 5000);
}
],
function (error, results) {
console.log(results);
});
One approach to do the above concurrently would be to use async.parallel - of the form of:
async.parallel([
function(){ ... },
function(){ ... }
], callback);
Another approach is to use a Promises library - BlueBird or Q are good choices.
Q.All is of the form of:
return Q.all([
promise1,
promise2
]).spread(function (resultFromPromise1, resultFromPromise2) {
// do something with the results...
});
You could use one of these approaches to parallelise the two calls. The outputs of each will give you an array containing the results of each call respectively.
Here is a simple illustration of each approach:
Using Async.js
var async = require('async');
var task = function (cb, count) {
setTimeout(function () {
cb(null, "complete: " + count);
}, 1000);
};
async.parallel([
function (cb) {
task(cb, 'one');
},
function (cb) {
task(cb, 'two');
}
], function (err, results) {
console.log(results);
//[ 'complete: one', 'complete: two' ]
});
Using Q:
var Q = require('q');
function task1(cb, count) {
var deferred = Q.defer();
setTimeout(function () {
return deferred.resolve(cb(null, count));
}, 1000);
return deferred.promise;
}
var myCb = function (err, count) {
return "complete: " + count;
};
Q.all([task1(myCb, 'one'), task1(myCb, 'two')])
.then(function (results) {
console.log(results);
//[ 'complete: one', 'complete: two' ]
});
Let me know if you are unclear.
Promises are there to help you out in such a case.
I would prefer to use 'Q' library.
I have modified your code to use Q library
var Q = require('q');
var request = require('request');
function makeCall() {
Q.all([req1(), req2()])
.spread(function (res1, res2) {
// This block is executed once all the functions( Provided in Q.all() ) are finished its execution.
// Use responses from called functions
}, function (err) {
// Error, If any
});
}
function req1() {
var defer = Q.defer();
var url = ''; // Specify URL
var options = {
method: 'get', // Method to use
url: url
}
request(options, function (err, res, body) {
if (err) {
return defer.reject(err);
}
return defer.resolve(body);
});
return defer.promise;
}
function req2() {
var defer = Q.defer();
var url = ''; // Specify URL
var options = {
method: 'get', // Method to use
url: url
}
request(options, function (err, res, body) {
if (err) {
return defer.reject(err);
}
return defer.resolve(body);
});
return defer.promise;
}
You can find docs for Q library here : Q docs
I am trying to convert email-templates node module into promise. I am using bluebird for promisification but it couldn't converted.
var emailTemplates = Promise.promisifyAll(require('email-templates'));
Is promisification node module supports this conversion or Am I doing any mistake?
EDITED :
I am doing like this now but wanna convert this to bluebird promise.
var emailTemplates = require('email-templates');
var path = require('path');
var templatesDir = path.resolve(__dirname, '../..', 'assets/templates');
var postmark = require('postmark');
var postmarkKey = MY_POSTMARK_KEY;
var postmarkClient = postmark(postmarkKey);
module.exports = {
sendEmail : function (templateName, locals, callback) {
emailTemplates(templatesDir, function (err, template) {
if (err)
return callback(err, null);
else {
template(templateName, locals, function (err, html, text) {
if (err) {
return callback(err, null);
}
else {
postmarkClient.send({
From: locals.from,
To: locals.to,
Subject: locals.subject,
HtmlBody: html
TextBody: text
}, function (err, responseStatus) {
if (err) {
return callback(err, null);
}
else {
return callback(err, responseStatus);
}
});
}
});
}
});
}
}
emailTemplates is a function, so you'd do:
var emailTemplates = Promise.promisify(require('email-templates'));
The problem is that it does not behave well since the function itself has a callback argument, so you'd have to do:
emailTemplates().then(function(template){
Promise.fromNode(template.bind(null, "template-name")).then(...
});
Promisify missbehaved for me as well, so i made a manual promisification.
var postmark = require("postmark");
var client = new postmark.Client("POSTMARK_API_TEST");
var Promise = require('bluebird');
exports.sendInviteEmail = function(email) {
let promise = new Promise((resolve, reject) => {
client.sendEmail({
"From": "donotreply#example.com",
"To": "target#example.us",
"Subject": "Test",
"TextBody": "Test Message"
}, function(error, result) {
if(error) {
reject(error);
} else {
resolve(result);
}
})
});
return promise;
}