I am trying to create a simple method that logs out my DB operation results in a prettified format in the console. I want to pass an extra variable (operationName) to my custom logger callback function when my DB finishes an operation.
// Custom logger (simplified)
const dbLogger = (error, data, operationName) => {
if (error) {
console.log(`${operationName} failed`, error)
}
console.log(`${operationName} success`, data)
}
// mongodb operations
import ChatModel from 'somewhere';
function createMessage() {
const newChatAdded = new ChatModel({
message: 'Hi'
})
ChatModel.save(newChatAdded, dbLogger);
}
Here the error and data in our callback is provided by the ChatModel.save method but I want to send a 3rd parameter operationName (eg. 'save' or 'delete') as well.
Two ways, take your pick!
use anonymous function with a rest parameter and spread these on calling dbLogger:
ChatModel.save(newChatAdded, (...args) => dbLogger(...args, "save");
use a bound function:
Change your dbLogger function signature to accept operationName as the first argument:
const dbLogger = (operationName, error, data) => {
if (error) {
console.log(`${operationName} failed`, error)
}
console.log(`${operationName} success`, data)
}
Create new bound log function which will always be called with "save" as the first argument.
ChatModel.save(newChatAdded, dbLogger.bind(null, "save");
This is also called "partial application".
ChatModel.save(newChatAdded, (error, data) => dbLogger(error, data, 'save'));
If you'd change the order of your parameters, you could make that more succinct:
const dbLogger = (operationName, error, data) => ...
ChatModel.save(newChatAdded, dbLogger.bind(null, 'save'));
Related
I have 2 sections of code 1) that is called by 2nd to populate the array and write it into a file.
async function timeSeries(obj) {
data = [
{
original_value: []
}
]
//read file named as passed object's _id
await fs.readFile("./api/assignment_data/" + obj._id + ".json", "utf-8", function read(err, datas) {
if (err) {
throw err;
}
const filedata = JSON.parse(datas)
filedata.map(line => data[0].original_value.push(line.original_value))
})
setTimeout(() => {
try {
fs.writeFileSync("./api/timeseries.json", JSON.stringify(data), { encoding: 'utf8', flag: 'w' })
} catch (error) {
console.log(error)
}
}, 300);
}
The problem is, I can't access the global data array above after using it inside the fs.readfile function ( callback scope hell problem), I had to setTimeout then I am able to write it inside a file using another fs.writeFileSync function ( if I return the array I get a promise, I want data).How do I solve this? instead of writing it into another file and using it inside another route(below) how can I directly return the array in the second route and pass it as a json res?
section 2)
router.route("/api/debug/:num").get((req, res) => {
fs.readFile("./api/assignment_data/metrics.json", "utf8", function read(err, data) {
if (err) {
console.log(err);
}
const objdata = JSON.parse(data)
timeSeries(objdata[req.params.num])
})
fs.readFile("./api/timeseries.json", "utf8", function read(err, data) {
if (err) {
console.log(err);
}
const objdata = JSON.parse(data)
res.json(data)
})
})
If you use fs.readFile and want to do an action after the file has been read, you must do the action (write and read a file in your case) inside the callback function. Also, you can use fs.readFileSync if you can read synchronously.
First off, we need to explain a few things:
fs.readFile() is non-blocking and asynchronous. That means that when you call it, it starts the operation and then returns immediately and starts the execute the code that comes right after it. Then, some time later, it calls its callback.
So, your code is:
Calling fs.readFile()
Then, immediately setting a timer
Then, it's an indeterminate race between the fs.readFile() callback and the timer to see who finishes first. If the timer finishes first, then it will call its callback and you will attempt to access data BEFORE it has been filled in (because the fs.readFile() callback has not yet been called).
You cannot write reliable code this way as you are guessing on the timing of indeterminate, asynchronous operations. Instead, you have to use the asynchronous result from within the callback because that's the only place that you know the timing for when it finished and thus when it's valid. So, one way to solve your problem is by chaining the asynchronous operations so you put the second one INSIDE the callback of the first:
function timeSeries(obj, callback) {
//read file named as passed object's _id
fs.readFile("./api/assignment_data/" + obj._id + ".json", "utf-8", function read(err, datas) {
if (err) {
console.log(err);
// tell caller about our error
callback(err)
return;
} else {
let data = [{original_value: []}];
const filedata = JSON.parse(datas);
for (let line of filedata) {
data[0].original_value.push(line.original_value);
}
fs.writeFile("./api/timeseries.json", JSON.stringify(data), { encoding: 'utf8' }, (err) => {
if (err) {
console.log(err);
callback(err);
return;
} else {
// give our data to the caller
callback(data);
}
});
}
})
}
Then, to call this function, you pass it a callback and in the callback you can either see the error or get the data.
In modern nodejs, it's a bit easier to use async/await and the promise-based interfaces in the fs module:
const fsp = require('fs').promises;
async function timeSeries(obj) {
//read file named as passed object's _id
try {
let datas = await fsp.readFile("./api/assignment_data/" + obj._id + ".json", "utf-8");
const filedata = JSON.parse(datas);
let data = [{original_value: []}];
for (let line of filedata) {
data[0].original_value.push(line.original_value);
}
await fsp.writeFile("./api/timeseries.json", JSON.stringify(data), { encoding: 'utf8' });
return data;
} catch(e) {
console.log(e);
// handle error here or throw back to the caller
throw e;
}
}
For this version, the caller can use await and try/catch to get errors:
try {
let data = await timeSeries(obj);
// do something with data here
} catch(e) {
// handle error here
}
Based on what code you have written , I could just modify it using simple async-await - hope this helps
import fs from 'fs'
async function timeSeries(obj) {
const data = [{
original_value: []
}]
const assData = fs.readFileSync('./api/assignment_data/metrics.json', 'utf8')
const filedata = JSON.parse(assData)
filedata.map(line => data[0].original_value.push(line.original_value))
// no need for timeOut
fs.writeFileSync('./api/timeseries.json', JSON.stringify(data));
//return data if u need
return data
}
router.route("/api/debug/:num").get(async (req, res) => {
try {
const metricData = fs.readFileSync('./api/assignment_data/metrics.json', 'utf8')
const objdata = JSON.parse(data)
const timeSeriesData = await timeSeries(objdata[req.params.num])
// returning TimeSeriesData
res.status(200).json(timeSeriesData)
})
}
catch (error) {
res.status(500).send(error.message)
}
I am trying to get data from a MongoDB collection so I created a database.js module to do this. One of the functions on the module was an exports.find function used to find data from a collection. I pass in a callback into the function because the Javascript is asynchronous and it takes time to retrieve data from the server. This is the code of the exports.find.
exports.find = (database, table, projection, callback) => {
MongoClient.connect(url, (err, db) => {
if (err) throw err
var dbo = db.db(database);
dbo.collection(table).find({}, projection).toArray(function(err, results) {
if (err) throw err
callback()
db.close()
});
});
}
I then use this function in my index.js file to use the found data and send it to the client. I did this by passing in res.render into my callback.
app.get('/', (req, res) => {
database.find('blog', 'blog-posts', {}, res.render('pages/index'))
})
Whenever I run this however, I get this error
TypeError: callback is not a function
at C:\Users\21Swag\Documents\Coding\Blog\database.js:23:13
The error points to the database.js file when I call the callback.
callback()
Does anyone know how to fix this? Any help would be appreciated.
Your callback is not a function, because you are already invoking it in database.find and then trying to call the return value of said function as your callback.
Maybe this will help:
const one = () => console.log(1)
const cbTest = (cb) => cb()
cbTest(one()) // Error: cb is not a function as it already returned 1
cbTest(one) // will console.log 1 as it is invoked correctly
Usually a callback is passed as an anonymous function like this:
database.find('blog', 'blog-posts', {}, () => res.render('pages/index'))
You can see the same pattern in the MongoClient.connect function/method. The second argument is the anonymous callback with (err, db) as parameters.
here is the picture
http.createServer((req,res)=>{});
how the createServer function knows that the callback function has 'req' for incoming message object and 'res' for server response object automatically
Can someone show me an example of how to create a function like createServer
http.createServer() has no idea what you declared for callback parameters. Regardless of how you declare your callback, http.createServer() passes two arguments to the callback when you call it. The first is the http request object, the second is the http response object. If your callback wants to work properly and use those arguments, it must create two arguments that match those. You can name them anything you want. The name is local only to your callback.
So, you can do any of these:
http.createServer((request, response) => {
response.end("hi");
});
http.createServer((req, res, goop) => {
// goop will be undefined here since no third argument is passed to the callback
res.end("hi");
});
http.createServer((a, b) => {
b.end("hi");
});
http.createServer((...args) => {
args[1].end("hi");
});
http.createServer(function() {
arguments[1].end("hi");
});
Can someone show me an example of how to create a function like createServer
You can create a function that accepts a callback function and then you call that callback with a set of arguments:
function readJSON(filename, callback) {
fs.readFile(filename, function(err, data) {
if (err) {
callback(err);
} else {
try {
let json = JSON.parse(data);
callback(null, json);
} catch(e) {
callback(e);
}
}
});
}
Here you create a function that takes a filename and a callback. It then reads that file, parses the JSON in it and calls the callback with null for the err and the parsed Javascript object for the 2nd parameter. If there's an error, then it just passes the error as the first argument.
HTTP is a class that contains 1 function called createServer, you can find some details on the HTTP object here https://nodejs.org/api/http.html, and when something like a request happens, the server returns your callback function with some parameters.
Hopefully, it will be easier to understand with 1 example:
You have the following class:
class User {
userInfo = {
name: "Jhon",
surname: "Doe"
}
getFullName = (callback) => {
return callback(this.userInfo.name, this.userInfo.surname)
}
}
This class has 2 defined properties, userInfo that contains the name and surname of the "user", and 1 function, getFullName, that function expects another function as is input, and whenever we call it, we will receive 2 parameters as output:
const UserInfo = new User()
UserInfo.getFullName((name,surname) => console.log(`Hello ${name} ${surname}`))
Hopefully, that answers your question, if you have any further questions or I completely misunderstood your question, feel free to tell me!
I have a function that fetch data from DB or API point and add properties to it, like this:
async function function_one (arg) {
try {
if (arg != number) throw new Error('error')
let data = await findOne(arg);
data['property'] = 1+2+3
....
return data //this is an object with it's own properties
} catch (e) {
console.log(e); //errorHandling with winston
}
}
and another (master) function that use data from previous function(s):
async function Master (user_input) {
try {
let object = await function_one(user_input);
console.log(object.property_one) //weak warning
let another_object = await another_function (fixed_arg);
//some other logic with object properties.
return result_data
} catch (e) {
console.log(e);
}
}
So when I'm trying to access object properties in Master function like:
let object = await function_one(user_input);
console.log(object.property_one)
My IDE (WebStrom) shows something like that: but I know that if function_one will execute correctly (w/o catch block) this property would exist. And if Master function will fail, user won't receive a message from function_one. (IDE says that throw exception caught locally).
So what am I doing wrong and what should I do? Handle every async function in Master function like that:
async function Master (user_input) {
try {
let object = await function_one(user_input)
.then(data => {
//working with properties here and then return it?
})
.catch(error => {//handle it});
or return from function_one all properties like: return {property_one, property_two, ... }
Using destructuring assignment helps me with this problem
I am using a library called node-geocoder in express js which has the following code:
var NodeGeocoder = require('node-geocoder');
var options = {
provider: 'google',
// Optional depending on the providers
httpAdapter: 'https', // Default
apiKey: 'YOUR_API_KEY', // for Mapquest, OpenCage, Google Premier
formatter: null // 'gpx', 'string', ...
};
var geocoder = NodeGeocoder(options);
// Using callback
geocoder.geocode('29 champs elysée paris', function(err, res) {
console.log(res);
});
The response variable(res) in the geocode method's callback function holds an object with location properties such as latitutde and longitude. The link for this package is here
I was wondering if there was a way to use that response variable outside of the callback function in the geocode method. I need to pull the latitude and longitude properties and I don't want to keep the rest of the code within that callback function.
As a noob I tried just returning the object and storing it in a variable like so:
var object = geocoder.geocode('29 champs elysée paris', function(err, res) {
return res;
});
This doesn't work since it's in the callback and not returned in the actual geocode method.
Not directly, but there are a couple of options to get closer to that.
One would be to have your callback be a defined function and use that as the callback:
const doSomething = (err, res) => { /* do something */ }
geocoder.geocode('abc', doSomething);
Not really much different, but can make it a little cleaner.
You can also "promisify" the function to have it return a Promise. Something like this would do the trick:
const geocodePromise = (path) => new Promise((resolve, reject) => {
geocoder.geocode(path, (err, res) => err ? reject(err) : resolve(res));
});
geocodePromise('abc')
.then(res => { /* do something */ })
.catch(err => { /* do something */ });
Finally, if you are using Babel for transpiling (or Node version 7.6.0 or higher, which has it natively), you can use async and await. Using the same promisified version of the function as above, you'd have to wrap your main code in an async function. I generally use a self-calling anonymous function for that:
(async () => {
try {
const res = await geocodePromise(path);
/* do something with res */
} catch (err) {
/* do something with err */
}
})();
With this, you get the closest to what you want, but you'll still have to wrap your main code up in a function because you can't await at the top level.
I don't want to keep the rest of the code within that callback function.
The code doesn't have to be in the callback, it just has to be called from the callback.
So you write your function (or functions) as normal:
function handleInfo(info) {
doSomethingWithInfo(info);
doSomethignElse(info);
// ...
}
// ...
...and then call those functions when you have the data:
geocoder.geocode('29 champs elysée paris', function(err, info) {
if (err) {
// Handle error
} else {
handleInfo(info);
}
});
You can use the response of your function outside the callback by calling another function.
geocoder.geocode('29 champs elysée paris', function(err, res) {
if(!err){
// call a function if there is no error
console.log(res);
myFunction();
}
});
function myFunction(){
//do your processing here
}
geocode can return promise already, no need to re-wrap it. If you don't care about the error and just want to grab the response's location data. You can do this
var locationData = geocoder.geocode('29 champs elysée paris').then( function(res){
return res;
});