I'm trying to refactor some node code that is a whole mess of callbacks. I thought that would be nice give promises a try for this purpose. I'm trying to convert some xml string to json with the xml2js node module. The original code was:
"use strict";
var xml2jsParser = require('xml2js').parseString;
var string = "<container><tag3>option3</tag3></container>";
xml2jsParser(string, function(err, result)
{
console.log(result);
});
and this displays:
{ container: { tag1: [ 'option1' ], tag2: [ 'option2' ], tag3: [ 'option3' ] } }
Following the first answer on this question How do I convert an existing callback API to promises? I tried to wrap the xml2jsParser function using promises in the following way:
"use strict";
var xml2jsParser = require('xml2js').parseString;
function promisesParser(string)
{
return new Promise(function(resolve, reject)
{
xml2jsParser(string, resolve);
});
}
var string = "<container><tag3>option3</tag3></container>";
promisesParser(string).then(function(err, result){
console.log(result);
});
This displays undefined through the console instead of the json object as expected. I don't understand why this happens as I was able to successfully do the same with other functions. I know something similar can be achieved with Bluebird promisify functionality but I'd like to do this on plain Javascript without any third party libraries.
Another option is to use native util module's promisify method, available from Node 8.0:
const xml2js = require('xml2js');
const util = require('util');
xml2js.parseStringPromise = util.promisify(xml2js.parseString);
// await xml2js.parseStringPromise(.. your xml ..);
You are going to need to wrap it up like this:
return new Promise(function(resolve, reject)
{
xml2jsParser(string, function(err, result){
if(err){
reject(err);
}
else {
resolve(result);
}
});
});
Then use it like this:
promisesParser(string).then(function(result){
console.log(result);
}).catch(function(err){
//error here
});
There are 2 issues...
You have to resolve with a value if it passes...and reject with an error when it fails
You need to add a catch block to you promise handling chain to catch errors.
var xml2jsParser = require('xml2js').parseString;
function promisesParser(string)
{
return new Promise(function(resolve, reject)
{
xml2jsParser(string, function(err, result) {
if (err) {
return reject(err);
} else {
return resolve(result);
}
});
});
}
var string = "<container><tag3>option3</tag3></container>";
promisesParser(string)
.then(console.log)
.catch(console.log);
I might be too late to this answer, but I thought to share what I have been using
One can use parseStringPromise method of xml2js with await keyword inside an async function.
import { parseStringPromise } from 'xml2js'
export const main = async () => {
const leadsData = await parseStringPromise(docBody)
console.log(leadsData)
}
Related
I am a beginner in javascript and am now trying to understand the subject of classes. I've defined the following class. Now I would like to use the result outside of this in a variable.
The class looks like this:
class Maad{
constructor(name, NumWeek, NumMonth){
this.name =name;
this.NumWeek = NumWeek;
this.NumMonth = NumMonth;
}
queryMaad(){
const mongodb =require('mongodb');
const client = require('mongodb').MongoClient;
const url= 'mongodb://localhost:27017/vertrieb';
client.connect(url,(error, db) =>{
if(!error){
console.log("month_log steht")
};
let col = db.collection("umsatz5");
col.aggregate([{'$match': {'AD': this.name}}, {'$match': {'Kalenderwoche':this.NumWeek}}, {'$count': 'procjetnumber'}],function(err, result){
if (err) {
console.error("Error calling", err);
}
console.log(result[0].projectnumber);
result[0].projectnumber;
})
db.close();
});
}
}
My request is:
let ma_1 = new Maad("Hans Wurst", NumWeek);
ma_1.queryMaad();
How can I save the result (the number of projects) in a variable to use it outside of the class? Thanks for your help.
In general, you would assign it basically the way that you assign anything:
const ma_1 = new Maad("Hans Wurst", NumWeek);
const myVar = ma_1.queryMaad();
However your method is a void method which doesn't return anything, so you need to edit your class if you want to get the number of projects.
Returning something from the function is harder than it sounds because MongoClient.connect is a void method which uses callbacks rather than returning a Promise of a response. Honestly I would recommend using a library like mongoose. But it is possible to make the method asynchronous ourselves by returning a new Promise which we resolve or reject based on the callbacks.
class Maad {
constructor(name, NumWeek, NumMonth) {
this.name = name;
this.NumWeek = NumWeek;
this.NumMonth = NumMonth;
}
async queryMaad() {
const url = "mongodb://localhost:27017/vertrieb";
return new Promise((resolve, reject) => {
MongoClient.connect(url, (error, db) => {
if (error) {
reject(error);
}
console.log("month_log steht");
let col = db.collection("umsatz5");
col.aggregate(
[
{ $match: { AD: this.name } },
{ $match: { Kalenderwoche: this.NumWeek } },
{ $count: "procjetnumber" }
],
function (err, result) {
if (err) {
reject(err);
}
resolve(result);
}
);
db.close();
});
});
}
}
Now queryMaad is an async method, one which returns a Promise. That Promise will resolve to the result of col.aggregate on success (you could also resolve to result[0].projectnumber). The promise will reject if there is an error in the connect or col.aggregate methods.
You now get your value like so (probably inside of a function which is itself async):
const result = await ma_1.queryMaad();
You can catch rejection errors here, or allow them to be thrown and catch them higher up.
try {
const result = await ma_1.queryMaad();
} catch (error) {
// console.error or whatever
}
Need to parse some XML files from mass array with file_path values.
Try to use async, fs, xml2js.
When use single string file_path all works perfect. But when I use aync.filter() with array I can't understand how I can return result from xml.parseString()
const fs = require('fs');
const xml2js = require('xml2js');
const async = require('async');
var mass=['/file1.xml','/fil2.xml','/file3.xml',...]
async.filter(mass, async function(file_path, callback){
if(fs.statSync(file_path)['size']>0){
fs.readFileSync(file_path, 'utf8', function(err, data) {
xml.parseString(data, function (err, result) {
console.log(Object.keys(result)[0]);
return result; //need get this result to results array
})
})
}
}, function(err, results) {
console.log(results)
});
Who can understand how it works and what I need to change in my code.
Thanks a lot!
You are trying to map and filter at the same time. Since your filter condition is synchronously available, use the array filter method for that, and then pass that to async.map.
You should then call the callback function, that async.map provides to you, passing it the result. So don't return it, but call the callback.
The readFileSync method does not take a callback like its asynchronous counterpart. It just returns the data.
Also, drop the async keyword, as you are not using the await keyword at all.
async.map(mass.filter((file_path) => fs.statSync(file_path).size > 0),
function(file_path, callback){
var data = fs.readFileSync(file_path, 'utf8');
xml.parseString(data, function (err, result) {
console.log(Object.keys(result)[0]);
callback(null, result);
})
}, function(err, results) {
console.log(results)
});
It should be noted however, that since Node now comes with the Promise API, and even the async/await extension to that, the async module has become much less interesting. Consider using Promises.
const promises = mass.filter(file_path => {
return fs.statSync(file_path).size > 0
}).map(function(file_path) {
return new Promise(resolve => {
const data = fs.readFileSync(file_path, 'utf8');
xml.parseString(data, function (err, result) {
console.log(Object.keys(result)[0]);
resolve(result);
});
});
});
Promise.all(promises).then(results => {
console.log(results);
});
I've node project.
Root file is index.js and file helper.js, here I've some helper functions and it imported to index.js.
I'm trying to get some data, using function in helper.js, but when I calling it in index.js it returning undefined.
But in helper.js everething is OK, console.log showing data that I need.
How I can fix this problem?
index.js file content:
const helper = require('./helper');
let data = helper.getData();
console.log(data); // undefined
helper.js file content:
const fs = require('fs');
module.exports = {
getData: () => {
fs.readFile('data.json', 'utf8', (err, data) => {
const allData = JSON.parse(data);
console.log(allData); // IS OK!
return allData;
});
}
}
You can use Promise:
const fs = require('fs');
module.exports = {
getData: () => {
return new Promise(function(resolve, reject){
fs.readFile('data.json', 'utf8', (err, data) => {
if(err){
reject(err);
} else {
try {
resolve(JSON.parse(data));
} catch(ex){
reject(ex);
}
}
});
});
}
}
and then:
helper.getData().then(function(data){
console.log(data);
}, function(err){
// here something failed
});
The problem is that fs.readFile method is asynchronous and will not give you as result any data check the documentation here.
So one option is to use a Promise as I did or to use a callback as suggested in the answer of #Tatsuyuki Ishi, you can check the docs about callback implementation.
The problem is that fs.readFile is an asynchronous function and so doesn't return anything.
If you really need it to return something you can use the synchronous version, fs.readFileSync.
Otherwise - and a better way to do it - would be to have getData return a promise that you can then resolve with allData.
readFile is an asynchronous function, which accepts a callback. You have two options:
1 . Get a callback as parameter in getData().
getData: (callback) => {
fs.readFile('data.json', 'utf8', (err, data) => {
const allData = JSON.parse(data);
console.log(allData); // IS OK!
callback(allData);
});
}
2 . Use the synchronous version.
getData: () => {
var data = fs.readFileSync('data.json', 'utf8');
const allData = JSON.parse(data);
console.log(allData); // IS OK!
return allData;
}
Of course, you can use Promise which is more beautiful on chaining things, but it's often used with dependencies like Bluebird.
The problem is, you are returning allData from the callback function, not the getData function. And since getData has no explicit return, your helper.getData() function will return undefined and this value would printed instead of what you wanted.
I suggest using Promise to return the data properly, as in #sand's answer.
I have a function that returns a Promise, that accesses the database and pulls a few lines out, assigning them to a Javascript variable.
The issue is that my '.then' clause is being triggered even though I know the Promise hasn't resolved:
app.post("/api/hashtag", function (req, res) {
FindPopularRumours().then(function (resolveVar) {
console.log(resolveVar);
console.log();
res.send(resolveVar);
}).catch(function () {
console.log("DB Error!");
res.send("DB Error!");
});
});
And the Promise function:
function FindPopularRumours() {
return new Promise((resolve, reject) => {
var hashtags = [];
var dbPromise;
db.collection(HASHTAGS).find().forEach(function (doc) {
hashtags.push(doc.hashtag);
console.log(hashtags);
});
resolve(hashtags);
});
}
The result output is:
[ ]
['#test1']
['#test1', '#test2']
['#test1', '#test2', '#test3']
As you can see, the first line ('[ ]') should ONLY be executed AFTER the hashtags have been output. But for some reason my code seems to think the Promise has been resolved before it actually has.
EDIT1
As per Ankit's suggestion, I have amended my function to:
function FindPopularRumours() {
return new Promise((resolve, reject) => {
var hashtags = [];
db.collection(HASHTAGS).find({}, function (err, doc) {
if (!err) {
doc.forEach(function (arg) {
hashtags.push(arg.hashtag);
console.log(hashtags);
});
resolve(hashtags);
} else {
return reject(err);
}
});
});
}
This still returns the same output response as before (e.g the 'then' clause is running before the promise itself).
My POST function is still the same as before.
The db.collection.find() function is async, so you have to resolve the promise inside the callback for that, something like
function FindPopularRumours() {
return db.collection(HASHTAGS).find().toArray().then( (items) => {
return items.map( doc => doc.hashtag);
});
}
takes advantage of the Mongo toArray() method, that returns a promise directly
Please note that db.collection(HASHTAGS).find() is an asynchronous call. So, your promise is resolved before database query returns. To solve this problem, you need to re-write your database query as follows:
function FindPopularRumours() {
return new Promise((resolve, reject) => {
var hashtags = [];
var dbPromise;
db.collection(HASHTAGS).find({}, function(err, doc){
if(!err){
doc.forEach(function (arg) {
hashtags.push(arg.hashtag);
console.log(hashtags);
});
resolve(hashtags);
}else{
return reject(err);
}
});
});
}
Hope the answer helps you!
I'm new to promise and bluebird, in my current project, I have to deal with a lot async API calls, therefore I want to use JS promise as my main tool.
One of my imported module looks like this:
var Promise = require("bluebird");
var watson = Promise.promisifyAll(require('watson-developer-cloud'));
var conversation = watson.init();
exports.enterMessage = function (inputText) {
var result; //want to return
conversation.message({
input: {
"text": inputText
}
}, function(err, response) {
if (err) {
console.log('error:', err);
result = err;
}
else {
result = response.output.text;
}
});
console.log(result); //sync call, result === undefined
return result;
}
My question is how should I approach this question? I understand the example about using promise with IO such like fs. So I try to mimic the way by doingconversation.message(...).then(function(response){result = response.output.text}), but it says conversation.message(...).then() is not defined.
Thanks to jfriend00's link, I fixed my logic and used the correct way to handle this async call.
Here is the fixed code:
//app.js
var Promise = require("bluebird");
var conversation = Promise.promisifyAll(require('./watson-conversation'));
conversation.enterMessage(currentInput).then(function(val){
res.send(val)}
).catch(function(err){
console.log(err)
});
});
//watson-conversation.js
var conversation = watson.init();
exports.enterMessage = function (inputText) {
return new Promise(function(resolve, reject){
conversation.message({
input: {
"text": inputText
}
}, function(err, response) {
if (err) {
console.log('error:', err);
reject(err);
}
else {
resolve(response.output.text);
}
});
});
}