node.js wait for initialize variable before load other function - javascript

request('GET', url_ws).done((res) => {
if (res.statusCode==200) {
parseString(res.getBody(),{explicitArray:false}, function (err, result) {
pdfAnnotations=result['root']['element'];
console.log(pdfAnnotations);//show value//second
});
}
});
console.log(pdfAnnotations);//display "undefined"//first
fn_work(pdfAnnotations)
Hello, i have to work with variable loaded from web service, but when my function starts, variable is 'undefined'

You need to call your function after parseString() is done:
request('GET', url_ws).done(res => {
if (res.statusCode == 200) {
parseString(res.getBody(), { explicitArray: false }, function (err, result) {
const pdfAnnotations = result['root']['element']
doSomething(pdfAnnotations)
})
}
})

this is normal because the code is executed asynchronosly, it makes the request and then executes fn_work right after that, while fetching data from url_ws , then when it gets the data, it moves on to ParseString and so on,
the easy way is to move fn_work(pdfAnnontaions) inside the callback of the ParseString like so
request('GET', url_ws).done((res) => {
if (res.statusCode==200) {
parseString(res.getBody(),{explicitArray:false}, function (err, result) {
pdfAnnotations=result['root']['element'];
fn_work(pdfAnnotations);
});
}
});
i would recommend using promises or async/await , check these out :
https://blog.risingstack.com/mastering-async-await-in-nodejs/
https://www.valentinog.com/blog/http-requests-node-js-async-await/#Making_HTTP_requests_with_Nodejs_the_request_module

Related

Convert a Promise Based Approach to Callbacks

I'm new to js. I've read many of the prior stack overflow posts on asynchronicity but still don't understand the question below.
I have the following code to upload an image file to an S3 bucket. The key thing the code needs to achieve is to have the image1 variable in the parent store the information in the data variable from the s3.upload call in the child function.
My code using promises below works fine. My understanding is that the same thing can be done using callbacks only, and I've been trying to rewrite the code below with callbacks only as a learning exercise, but it has not worked. How would I change this code to do the same thing with callbacks and no promises?
Parent function:
try {
image1 = await uploadToAWSBucket(file);
}
catch (error) {
return next(error);
}
Child:
const uploadToAWSBucket = async (fileObject) => {
let randomFileName = shortid.generate();
const AWSUploadObject = {
Bucket: BUCKET_NAME,
Key: randomFileName,
Body: fileObject.buffer,
ContentType: fileObject.mimetype,
};
return new Promise((resolve, reject) => {
s3.upload(AWSUploadObject, (err, data) => {
if (err) {
return reject(err);
}
return resolve(data);
});
});
};
At first, you need to add a callback arg to you async function, removing async keyword
const uploadToAWSBucket = (fileObject, callback) => {
Next, you need to handle s3 response in a callback manner replacing Promise with callback usage.
s3.upload(AWSUploadObject, (err, data) => {
if (err) {
callback(err);
return;
}
callback(null, data);
});
Or maybe you can even simplify it to
s3.upload(AWSUploadObject, callback)
You also need to update your usage to a callback manner
uploadToAWSBucket(file, (error, image1) => {
if (error) {
next(error);
return;
}
// your success code here
});
The final result is
const uploadToAWSBucket = (fileObject, callback) => {
let randomFileName = shortid.generate();
const AWSUploadObject = {
Bucket: BUCKET_NAME,
Key: randomFileName,
Body: fileObject.buffer,
ContentType: fileObject.mimetype,
};
s3.upload(AWSUploadObject, callback);
};
That's it. I hope this explanation will help you to understand how to use callbacks.
If my understanding is correct, you want to use image1 after the catch block.
In that case, I suppose, you will be calling some function with image1. It can be done as follows, with some snippets taken from this answer:
const uploadToAWSBucket = (fileObject, callback) => { ... }; // described in the linked answer
uploadToAWSBucket(file, function callback(error, image1) {
if(error) { return next(error); }
someOtherFunction(image1, next); // "next" is passed as callback, with the assumption that nothing else needed to be called after that.
});
If you want to call 2 more functions with the result of someOtherFunction, it can be done as follows:
uploadToAWSBucket(file, function callback(error, image1) {
if(error) { return next(error); }
someOtherFunction(image1, function someOtherFunctionCb(error, someOtherFunctionResult) {
if(error) { return next(error); }
someOtherFunction2(someOtherFunctionResult, function someOtherFunction2Cb(error, someOtherFunction2Result) {
if(error) { return next(error); }
someOtherFunction3(someOtherFunction2Result, function someOtherFunction3Cb(error, someOtherFunction3Result) {
if(error) { return next(error); }
next(null, someOtherFunction3Result);
});
});
});
});
Basically, you cannot have local global variables if you use callbacks. I will try to explain a problem situation.
let image1 = null;
uploadToAWSBucket(file, function uploadToAWSBucketCallback(error, _image1) {
if(error) { return next(error); }
image1 = _image1;
});
someOtherFunction(image1, function someOtherFunctionCb(error, someOtherFunctionResult) {
if(error) { return next(error); }
...
});
In the above snippet, someOtherFunction will be called before uploadToAWSBucketCallback is executed. That means, image1 is not assigned with _image1. Now, you know what will be the value of image1 when someOtherFunction is called.
The second snippet shows how to pass result of one async function to another, by nesting the subsequent calls inside the callbacks. This makes code less readable for many. There are libraries like async, which helps to make things easier & readable.
The second snippet can be rewritten with async library's waterfall function like this:
async.waterfall([
function uploadToAWSBucketStep(callback) {
uploadToAWSBucket(file, callback);
},
function someOtherFunctionStep(image1, callback) {
someOtherFunction(image1, callback);
},
function someOtherFunction2Step(someOtherFunctionResult, callback) {
someOtherFunction2(someOtherFunctionResult, callback);
},
function someOtherFunction3Step(someOtherFunction2Result, callback) {
someOtherFunction3(someOtherFunction2Result, callback);
}
], function lastStep(error, someOtherFunction3Result) {
if(error) { return next(error); };
next(null, someOtherFunction3Result);
});
Promisifcation of a callback-based function is well understood and well documented.
I have never seen a discussion of "de-promisification", but it is pretty simple.
Starting with your uploadToAWSBucket() and assuming you want your callback to be "nodeback" style (signature (err, data)), then you can write:
const uploadToAWSBucketNodeback = (fileObject, nodeback) => {
uploadToAWSBucket(fileObject) // call the promise-returning "async" version.
.then(data => { // success path
nodeback(null, data);
})
.catch(nodeback); // error path
};
Or you could write a generic de-promisifier ...
const depromisify = (asyncFunction) => {
return function(...params) {
let nodeback = params.pop(); // strip nodeback off the end of params
asyncFunction(...params)
.then(data => { // success path
nodeback(null, data);
})
.catch(nodeback); // error path
}
};
... then
const uploadToAWSBucketNodeback = depromisify(uploadToAWSBucket);
Either approach will allow you to write:
uploadToAWSBucketNodeback(fileObject, function(err, data)) {
if(err) {
// handle error
} else {
// handle data
}
}
Notes
we just need to know that the original asyncFunction is thenable/catchable.
the original asyncFunction is completely opaque to the depromisified function.
we don't need to know anything about the internal workings of the original asyncFunction. Thus, the composition of AWSUploadObject doesn't need to be replicated ..... it is still performed by uploadToAWSBucket().
After reading everyone's responses, I came up with the following which I think works and is basically what #Pavlo Zhukov suggested. (Note the function names have changed slightly from my earlier post.)
Code from parent function:
let image1;
uploadToAWSBucketCallbackStyle(file, (err, data) => {
if (err) {
return next(err);
}
image1 = data;
// Do stuff with image1 here or make additional function
// calls using image1.
});
Child function:
const uploadToAWSBucketCallbackStyle = (fileObject, callback) => {
let randomFileName = shortid.generate();
const AWSUploadObject = {
Bucket: BUCKET_NAME,
Key: randomFileName,
Body: fileObject.buffer,
ContentType: fileObject.mimetype,
};
s3.upload(AWSUploadObject, callback);
}

Mongoose function logs correct data but using async function to get it returns undefined

I am trying to query some data from a database and return it to a function. The issue I am having is that when I log the results from the query it is working but when I try to query what the function is returning I am getting undefined. It could be that my async function with mongoose isn't exactly set up correctly. Or maybe I just have the wrong idea of what is happening as I am new to the idea of asynchronous programming.
async function returnBlogThumbnails(filter = "recent", callback){
console.log("returning blogs")
//For now simply filter by most recent
if(filter === "recent"){
Blog.find({}).sort('-date').exec((err,docs) => {
return docs;
});
}
}
and the function that calls this function
app.get('/', (req, res)=> {
console.log("go home");
//Call out to query the database then use async function to return
database.returnBlogThumbnails().then((blogs) => {
console.log(blogs);
//res.render('home', blogs);
});
});
As I have said the console.log spits out what I am looking for. However, the function calling the function with the query always shows undefined. Thanks
The problem is you defined your function with callback and trying to call it as a promise. Your function with promise should be:
async function returnBlogThumbnails(filter = "recent") {
return new Promise((resolve, reject) => { // creates a promise
console.log("returning blogs")
//For now simply filter by most recent
if (filter === "recent") {
Blog.find({}).sort('-date').exec((err, docs) => {
if (err) {
reject(err);
} else {
resolve(docs)
}
});
}
});
}
and then in your route you can call it as:
app.get('/', (req, res) => {
console.log("go home");
//Call out to query the database then use async function to return
database.returnBlogThumbnails()
.then((blogs) => {
console.log(blogs);
// do something with blogs
})
.catch((err) => {
console.log(err);
// handle error
});
});

How do I get the result from this async function?

Problem
I'm trying to get the diskName value back from this.getDiskName('C:')
and assign it to element['name']:
getDisksInfo () {
...
element['name'] = this.getDiskName('C:')
...
},
getDiskName (diskLetter) {
if (process.platform == 'win32') {
var exec = require('child_process').exec
var cmd = `wmic logicaldisk where caption="${diskLetter}" get VolumeName`
exec(cmd, (err, stdout, stderr) => {
if (err) {
console.log(err)
}
let diskName = stdout.split('\n')[1]
return diskName
})
}
}
I tried doing this, but I keep getting different errors:
getDiskName (diskLetter, callback) {
...
exec(cmd, (err, stdout, stderr) => {
if callback(null, () => {
let diskName = stdout.split('\n')[1]
return diskName
})
...
}
Question
Could someone please explain how to return the value properly?
Your problem is that you are missing either a callback coming into getDiskName() or a Promise() coming out.
Since the Promise approach seems to be more popular nowadays, I'll go with that for this answer.
With a Promise approach, you need the function to return a Promise. In most cases, you just wrap all the code up in a Promise and return that:
getDiskName(diskLetter) {
return new Promise((resolve, reject) => {
// rest of your code in the function
});
}
Then, instead of your return, you'll call resolve():
let diskName = stdout.split('\n')[1];
resolve(diskName)
And for your error, you'll call reject:
if (err) {
reject(err);
}
Then, in the function that uses it, you'll have to wait for the then() in your function:
this.getDiskName('C:').then(diskName => console.log(diskName))
The callback method is similar, you just pass in the callback into getDiskName and call it when you're ready.
This is a more idiomatic method to handle a case like this. We'll pass a function in to getDiskName which takes the disk name (which is the return value) as a parameter.
getDisksInfo () {
...
this.getDiskName('C:', function(diskName) {
element['name'] = diskName;
});
// Note that code from here to the end doesn't have access
// to element['name']
...
},
getDiskName (diskLetter, func) {
if (process.platform == 'win32') {
var exec = require('child_process').exec
var cmd = `wmic logicaldisk where caption="${diskLetter}" get VolumeName`
exec(cmd, (err, stdout, stderr) => {
if (err) {
console.log(err)
}
let diskName = stdout.split('\n')[1]
func(diskName);
})
}
}
Now, this still might not work for you since perhaps you have code after the call which relies on knowing the diskName. In that case, you would probably roll that code into your anonymous function. Perhaps getDisksInfo takes a function as a parameter instead.
This is the general pattern, you have to determine how it best fits in your program.

Having problems with node and async

I'm trying to excute async-waterfall to get api result and save it to json then save it to database,
thats snippet of my code
Please Help!
async.waterfall([
function getBook(cbAsync) {
books.search(query, (err, result) => {
if (err)
cbAsync(err)
res.json(result)
})
},
function saveToJson(saveToJsonCb, cbAsync) {
jsonfile.writeFile(file, result, (err) => {
if (err)
cbAsync(err)
})
},
function SaveToDb(saveCb, saveToJsonCb, cbAsync) {
const book = {
title: res.body.title,
authors: [res.body.authors],
description: res.body.description
}
//save the bookInfo to db
book.save( (err) => {
if (err)
cbAsync(err)
console.log('Book added!')
})
}
], function asyncComplete(err) {
if (err) {
console.warn('Error')
} else {
console.info('Task complete with success')
}
})
The first task only calls the callback if it encounters an error, which in a perfect condition, will never be called.
Note:
1. Every 'task' must call a callback.
2. Make sure that the callback is called before each function finishes.
Follow the pattern indicated in the Async Documentation
http://caolan.github.io/async/docs.html#waterfall

async each callback is called before iteration

i have the following routing function:
router.route('/api/teamUsersWithStat/:team_id')
.get(function (req, res) {
var user_stat = academy_team_user_stat.build();
user_stat.usersByTeam(req.params.team_id, function (result) {
if (result) {
async.each(result, function () {
var i = 0;
user_stat.findModulesTaken(res.user_id, res.team_id, function (modules) {
result[i].modules = modules;
i++;
});
}, res.json(result))
} else {
res.status(401).send("Team not found");
}
}, function (error) {
res.send("Team not found");
});
});
as you can see im using the async.each method to collect additional data to my existing array.
However the res.json(result) is called without it running the actual loop.
(i can tell this as in my javascript i am debugging the response).
So what am i doing wrong?
You're calling your res.json method straight away, you're also reinitializing i inside the loop so it's always 0.
Also, each requires a callback in order to procede to the next iteration.
The following is how I'd do it:
async.each(result, function (r, callback) {
user_stat.findModulesTaken(res.user_id, res.team_id, function (modules) {
result[result.indexOf(r)].modules = modules;
callback();
});
}, function(err) {
if(err)
return res.json(err);
res.json(result);
});
res.json(result) is called as a function, and therefore invoked immediately. To make sure res.json is invoked after the async.each(), you need to pass a function as callback:
async.each(result, function () {
...
}, function(err) {
if(!err) res.json(result);
));

Categories

Resources