I am still struggling with the nested callback structure of Node.js. I have looked as async, co and other methods, but they do not seem to help.
What is the best practice on how to code, e.g.
var db = MongoClient.connect(url, callback1 () {
if (auth) }
db.authenticate(user, pswd, callback2 () {
--- should continue with db.collection.find (authenticated)
)
--- should continue with db.collection.find (non-authenticated)
}
So the question ist: How should I code this sequence to be able to execute the db calls following db.connect or db.authenticate (and both callbacks are completed)? The only way I can think of is to have the following db-calls in a separate function and call this function in both callback routines. Not really elegant ...
If what you are confused by is how to put optional conditions before a callback, using async you can do:
var async = require('async');
var db = MongoClient.connect(url, () => {
async.series([
(callback) => {
//Branching
if(auth) {
// Do conditional execution
db.authenticate(user, pswd, () => {
callback();
});
} else {
// Skip to the next step
callback();
}
},
(callback) => {
// Whatever happened in the previous function, we get here and can call the db
db.collection.find();
}
]);
});
I'm not sure that I fully understand what you are asking, by the way, if you want to run a callback depending on some conditions (eg: requires or not authentication)... you can use promises:
var db = MongoClient.connect(url, callback1 () {
if (auth) }
db.authenticate(user, pswd, callback2 () {
--- should continue with db.collection.find (authenticated)
)
--- should continue with db.collection.find (non-authenticated)
}
var MongoClientConnect = (url, username, password) => new Promise((resolve, reject) => {
var db = MongoClient
.connect(url, () => {
if(!requiresAuthentication) {
return resolve(db);
}
db.authenticate(username, password, () => {
//check if login success and
resolve(db);
});
})
;
});
// THEN
MongoClientConnect()
.then(db => db.collection.find("bla bla bla"))
;
Related
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);
}
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
});
});
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.
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
I'm trying to set a class member variable from a callback of a function I'm calling from the class constructor.
To be a bit more specific: I need to set the connection ID in the Connection class constructor based on the Redis INCR result (each client has a 'global' connection ID so I can have multiple nodes).
Here's the code.
class Connection {
constructor() {
client.incr('conn_id', (err, reply) => {
this.connID = reply;
});
}
}
var lovely = new Connection();
console.log(`lovely connID is ${ lovely.connID }`);
This is the result: lovely connID is undefined
It seems that client.incr('conn_id' ....) is async , which means the callback will be invoked after your code run .
So
console.log(lovely connID is ${ lovely.connID }); will be called before the callback
(err, reply) => {
self.connID = reply;
}
which is similar to this :
class Connection{
constructor(){
self=this;
setTimeout( function(){self.client='somevalue';
console.log('value1');}, 10)
}
}
var a = new Connection();
console.log(a.client);
running this will result
undefined
value1
As others here have mentioned, the issue seems to be that client.incr is asynchronous and your code does not wait for it to resolve before accessing the properties. To remedy this issue, you could try passing in an onReady callback to Connection to enssure the properties will be there before trying to access them. Something along these lines:
'use strict';
// mock client.incr
var client = { incr: (id, callback) => {
setTimeout(() => callback(null, 'Hello World'), 0)
}};
class Connection {
// receive an "onReady" function
constructor(onReady) {
client.incr('conn_id', (err, reply) => {
this.connID = reply;
// call "onReady" function, and pass it the class
if (typeof onReady === 'function') onReady(this)
});
}
}
new Connection(lovely => { console.log(lovely.connID) })
I hope that helps!
In general putting heavy initialization logic in a constructor, especially if it's asynchronous, is not a good idea. As you've found, the constructor has no way to return the information about when the initialization is finished. An alternative is to create a promise for when the connection is ready. Then, in your outside code, you can hang a then off the property to specify the code you want to trun when it's ready.
class Connection {
constructor() {
this.connId = new Promise((resolve, reject) =>
client.incr('conn_id', (err, reply) => {
if (err) return reject(err);
resolve(reply);
});
}
}
var lovely = new Connection();
lovely.connId . then(connId => console.log(`lovely connID is ${ connID }`);