I am using async.js to help with validation and transformations for data imports. After moving a piece of code to it's own function I get the error "Callback was already called." from async.js. The stack trace has no useful information on debugging. The exact code works fine when I call it directly. I need to use it across multiple imports so that's why I moved to the an object containing other "transformers". They all work fine.
I've tried inserting the following code into the function to try to debug:
return cb("test);
It runs fine and returns the error "test" to the browser if I add it to the first line of the function. Inserting it anywhere else causes the error.
The following works. If I move "return cb("error);" to the line after let query = ... it will fail with the callback error. I'm quite confused.
user(options = {
select: "id",
return: "id",
lean: true,
}) {
return (value, cb) => {
if (!value) {
return cb(null, null);
}
return cb("test");
let query = User.findOne({
$or: [{
email: value.toLowerCase()
},
{
username: value
},
]
}).select(options.select);
if (options.lean) {
query.lean();
}
query.exec((error, user) => {
if (error) {
return cb(error);
}
if (!user) {
return cb(`user not found: ${value}`);
}
if (options.return == "object") {
cb(null, user);
} else {
cb(null, user._id);
}
});
}
}
The variable "cb" is passed from an async call:
async.mapValues(self.options.validators, (validators, key, cb) => {
//Get the value that is being validated
var value = _.get(document, key);
var isArray = false;
//Enforce array type
if (!Array.isArray(validators)) {
validators = [validators];
}
//Transform the value before validating it
var transformer = _.get(self.options.transformers, key);
//Don't run transformer if empty value
if (!transformer || value == null) {
transformer = (value, cb) => cb(null, value);
}
transformer(value, (error, newValue) => {
//Check error
if (error) {
return _cb(error);
}
//Set new value
value = newValue;
_.set(document, key, newValue);
Related
Working in a NodeJS that saves data to a SQL Server Database, it must save data from an array of objects but when I run it I get this error, just looked here and documentation but I don't really understand how to fix it, any help is welcomed. This is the error:
PS D:\Users\****\****\****\****\****> node appb.js
Successful connection
events.js:135
throw new ERR_INVALID_ARG_TYPE('listener', 'Function', listener);
^
TypeError [ERR_INVALID_ARG_TYPE]: The "listener" argument must be of type function. Received type string ('row')
And this is my app.js:
Connection:
var Connection = require("tedious").Connection;
var lstValid = [];
var config = {
server: "SERVER",
authentication: {
type: "default",
options: {
userName: "USERNAME",
password: "PASSWORD",
},
},
options: {
encrypt: true,
database: "DATABASE",
instanceName: 'INSTANCENAME'
},
};
var connection = new Connection(config);
connection.on("connect", function (err) {
console.log("Successful connection");
executeStatement1();
});
connection.connect();
and here's where I insert data:
async function calcWeather() {
const info = await fetch("../json/data.json")
.then(function (response) {
return response.json();
});
for (var i in info) {
const _idOficina = info[i][0].IdOficina;
const lat = info[i][0].latjson;
const long = info[i][0].lonjson;
const base = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${long}&appid=${api_key}&units=metric&lang=sp`;
fetch(base)
.then((responses) => {
return responses.json();
})
.then((data) => {
var myObject = {
Id_Oficina: _idOficina,
// Other thins in myObject
};
// validation and saving data to array
if (myObject.Temperatura < 99) {
lstValid.push(myObject);
}
});
}
}
var Request = require("tedious").Request;
var TYPES = require("tedious").TYPES;
function executeStatement1() {
calcWeather();
for (var m = 0; m <= lstValid.length; m++) {
Request = new Request(
"INSERT INTO TB_BI_CSL_RegistroTemperaturaXidOdicina (IdOficina, Humedad, Nubes, Sensacion, Temperatura, Descripcion) VALUES (#IdOficina, #Humedad, #Nubes, #Sensacion, #Temperatura)",
function (err) {
if (err) {
console.log("Couldn't insert data: " + err);
}
}
);
Request.addParameter("IdOficina", TYPES.SmallInt, lstValid[m]);
// Other things inserted
Request.on('requestCompleted',"row", function (columns) {
columns.forEach(function (column) {
if (column.value === null) {
console.log("NULL");
} else {
console.log("Product id of inserted item is " + column.value);
}
});
});
Request.on("requestCompleted", function (rowCount, more) {
connection.close();
});
connection.execSql(Request);
}
}
Seems that there are too many parameters to the request.on(...) method, i.e.:
Request.on('requestCompleted',"row", function (columns)
Should probably be:
Request.on("row", function (columns)
The error says the JavaScript function received arguments that were different than expected:
...ERR_INVALID_ARG_TYPE('listener', 'Function', listener);
If it has never worked, the function was likely mistyped. (If it has worked, it could be bad data coming in)
The next message gives a further information:
"...The "listener" argument must be of type function. Received type string ('row')"
A JavaScript function to do work was expected, but it received a simple string 'row' instead.
events.js:135
This means the error happened in the file 'events.js' on or before line 135.
The TediusJs API Request Docs, provides a reference example:
request.on('row', function (columns) { /* code to process rows */ });
In your example we find:
Request.on('requestCompleted',"row", function (columns) {
Most likely it should be:
Request.on("row", function (columns) {
Although I am not positive which line in your example is line 135.
I know this topic as already asked many times before but I didn't find the right answer to do what I want.
Actually, I try to save two different list of JSON object in MongoDB via Mongoose. To perform both at the same time I use 'async'.
However, when I save it with the command insertMany() I get an error because he calls the callback of async before finishing the insertMany(). Therefore answer[0] is not defined.
What will be the proper way of doing it ?
Here is my code with the async:
const mongoose = require("mongoose");
const async = require("async");
const utils = require("../utils");
const experimentCreate = function(req, res) {
let resData = {};
let experimentList = req.body.experiment;
let datasetList = req.body.datasetList;
async.parallel(
{
dataset: function(callback) {
setTimeout(function() {
answer = utils.createDataset(datasetList);
callback(answer[0], answer[1]);
}, 100);
},
experiment: function(callback) {
setTimeout(function() {
answer = utils.createExp(experimentList);
callback(answer[0], answer[1]);
}, 100);
}
},
function(err, result) {
if (err) {
console.log("Error dataset or metadata creation: " + err);
sendJSONresponse(res, 404, err);
} else {
console.log("Experiment created.");
resData.push(result.dataset);
resData.push(result.experiment);
console.log(resData);
sendJSONresponse(res, 200, resData);
}
}
);
};
Then the two functions called createExp and createDataset are the same in another file. Like this:
const createDataset = function(list) {
let datasetList = [];
for (item of list) {
let temp = {
_id: mongoose.Types.ObjectId(),
name: item.name,
description: item.description,
type: item.type,
};
datasetList.push(temp);
}
Dataset.insertMany(datasetList, (err, ds) => {
if (err) {
console.log("Error dataset creation: " + err);
return [err, null];
} else {
console.log("All dataset created.");
return [null, ds];
}
});
};
There's a few problems with your code. For one, you're not returning anything in your createDataset function. You're returning a value in the callback of insertMany but it doesn't return that value to the caller of createDataset as it's within another scope. To solve this issue, you can wrap your Dataset.insertMany in a promise, and resolve or reject depending on the result of Data.insertMany like this:
const createDataset = function(list) {
let datasetList = [];
for (item of list) {
let temp = {
_id: mongoose.Types.ObjectId(),
name: item.name,
description: item.description,
type: item.type,
};
datasetList.push(temp);
}
return new Promise((resolve, reject) => {
Dataset.insertMany(datasetList, (err, ds) => {
if (err) {
console.log("Error dataset creation: " + err);
reject(err);
} else {
console.log("All dataset created.");
resolve(ds);
}
});
});
};
Now your return object is no longer going to be an array so you won't be able to access both the error and the result via answer[0] and answer[1]. You're going to need to chain a then call after you call createDataset and use callback(null, answer) in the then call (as that means createDataset executed successfully) or use callback(err) if createDataset throws an error like below:
dataset: function(callback) {
setTimeout(function() {
utils.createDataset(datasetList).then(answer => {
callback(null, answer);
}).catch(err => callback(err)); // handle error here);
}, 100);
}
Note: You'll most likely need to alter your createExp code to be structurally similar to what I've produced above if it's also utilizing asynchronous functions.
I have an Airtable base that I can retrieve records from (see code below), but I'd like to get the value for other fields besides just "Location". Using "console.log('Retrieved: ', record.get('Location'));", how do I modify this line to include in the output the field values for a field called "Size" in addition to the "Location" field? I tried "console.log('Retrieved: ', record.get('Location', 'Size'));", but that didn't work.
Here's an excerpt from my code:
// Lists 3 records in Bins
base('Bins').select({
// Selecting the first 3 records in Grid view:
maxRecords: 3,
view: "Grid view"
}).eachPage(function page(records, fetchNextPage) {
// This function (`page`) will get called for each page of records.
records.forEach(function(record) {
console.log('Retrieved: ', record.get('Location'));
});
// To fetch the next page of records, call `fetchNextPage`.
// If there are more records, `page` will get called again.
// If there are no more records, `done` will get called.
fetchNextPage();
}, function done(err) {
if (err) { console.error(err); return; }
});
OUTPUT
Retrieved 170000118
Retrieved 170000119
Retrieved 170000120
I found this repo to help in when I tried to product situations like this.
A wrapper for common functions for accessing data on an airtable.com database. All queries return promises.
Here is how it works if you want to avoid using an npm package. But ultimatly the jist of it is to either use request or some short of promise fulfillment menthod to retrive the Records.
import Airtable from 'airtable'
import _ from 'lodash'
const ENDPOINT_URL = 'https://api.airtable.com'
let API_KEY // Can only set the API key once per program
export default class AirTable {
constructor({apiKey, databaseRef}) {
if(!API_KEY) {
API_KEY = apiKey
Airtable.configure({
endpointUrl: ENDPOINT_URL,
apiKey: API_KEY
});
}
this.base = Airtable.base(databaseRef)
this.get = {
single: this.getSingleRecordFrom.bind(this),
all: this.getAllRecordsFrom.bind(this),
match: this.getAllMatchedRecordsFrom.bind(this),
select: this.getRecordsSelect.bind(this)
}
this.insert = this.createRecord.bind(this)
this.add = this.insert
this.create = this.insert
this.update = this.updateRecord.bind(this)
this.set = this.update
this.remove = this.deleteRecord.bind(this)
this.delete = this.remove
this.destroy = this.remove
this.rem = this.remove
}
async createRecord({tableName, data}) {
return new Promise((resolve, reject) => {
this.base(tableName).create(data, (err, record) => {
if (err) {
console.error(err)
reject()
return
}
console.log("Created " + record.getId())
resolve(record)
})
})
}
async updateRecord({tableName, id, data}) {
return new Promise((resolve, reject) => {
this.base(tableName).update(id, data, (err, record) => {
if (err) {
console.error(err)
reject()
return
}
console.log("Updated " + record.getId())
resolve(record)
})
})
}
async deleteRecord({tableName, id, data}) {
return new Promise((resolve, reject) => {
this.base(tableName).destroy(id, (err, record) => {
if (err) {
console.error(err)
reject()
return
}
console.log("Deleted " + record.getId())
resolve(record)
})
})
}
async getSingleRecordFrom({tableName, id}) {
console.log(tableName, id)
return new Promise((resolve, reject) => {
this.base(tableName).find(id, function(err, record) {
if (err) {
console.error(err)
reject(err)
}
resolve(record)
})
// console.log(record);
})
}
async getAllRecordsFrom(tableName) {
return this.getRecordsSelect({tableName, select: {} })
}
async getAllMatchedRecordsFrom({tableName, column, value}) {
return this.getRecordsSelect({tableName, select: {filterByFormula:`${column} = ${value}`} }) // TODO: validate input
}
async getRecordsSelect({tableName, select}) {
return new Promise((resolve, reject) => {
let out = []
this.base(tableName).select(select).eachPage((records, fetchNextPage) => {
// Flatten single entry arrays, need to remove this hacky shit.
_.map(records, r => {
_.forOwn(r.fields, (value, key) => { // If array is single
if(_.isArray(value) && value.length == 1 && key != 'rooms') {
r.fields[key] = value[0]
}
});
})
out = _.concat(out, records)
fetchNextPage();
}, (err) => {
if (err) {
console.error(err)
reject(err)
} else {
// console.log(JSON.stringify(out, null, 4))
// console.log("HI")
resolve(out)
}
})
})
}
}
Hope this Makes sense, Also trying to make an API-Proxy fetching a whole table or even use Express to fetch record id's as arrays can work as well
You can use this code line.
records.forEach(function(record) {
console.log('Retrieved: ', record.get('Location') + ' ' + record.get('Size'));
});
The async function itself should make use of the lookup() function what I have used inside the async function,but return the result inside the callback.
The parameters for the callback are err and res.
If an Error has been thrown by lookup() then it should be passed to
err, otherwise err is null or undefined.If a result has been returned by lookup() then it should be passed to res, otherwise res is null or undefined,I have other two tests for checking property as like user but I have shortened the code as much as possible. the problem is callback inside lookupAsync() function.
const users = [
{
"login": "norvig",
"firstName": "Peter",
"lastName": "Norvig",
"likes": ["AI", "Search", "NASA", "Mars"]
}
];
// lookupAsync()
const lookupAsync = (login, prop, callback) => {
// Only change code below this line
const found = users.find(function(e) {
return e.login === login;
});
if (!found) {
throw "Could not find user.";
} else {
if (prop in found) {
return found[prop];
} else {
throw "Could not find property";
}
}
//my current concept according to suggestion but trying to set in the
code.
function mycallback(callback) {
var err,res;
callback(err,res);
}
mycallback( function() {
console.log();
});
};
test('lookupAsync() likes', assert => {
const msg = `lookupAsync(<login>, 'likes', callback) should return
likes for the specified user.`;
lookupAsync('norvig', 'likes', function(err, res){
const actual = res;
const expected = ["AI", "Search", "NASA", "Mars"];
assert.deepEqual(actual, expected, msg);
assert.end();
});
});
test('lookupAsync() with unknown user', assert => {
const msg = `lookupAsync() with unknown user should return an error
with the correct message.`;
const value = lookupAsync('nobody', 'likes', function(err, res){
const actual = err.message;
const expected = 'Could not find user.';
assert.equal(actual, expected, msg);
assert.end();
});
});
Let me do it for you
const lookupAsync = (login, prop, callback) => {
const found = users.find(function(e) {
return e.login === login;
});
if (!found) {
callback(new Error("Could not find user."));
} else {
if (prop in found) {
callback(null, found[prop]);
} else {
callback(new Error("Could not find property"));
}
}
}
I my Node backend have the following end-point:
usersRoute.get('/get', function(req, res) {
//If no date was passed in - just use todays date
var date = req.query.date || dateFormat(new Date(), 'yyyy-mm-dd'),
search = req.query.search;
users.getAllUsers(date, search)
.then(function(results) {
res.json(results);
}, function(err) {
res.status(500).json({
success: false,
message: 'Server error.',
data: []
});
});
});
I have changed my sql table name to something else to trigger the function(err){} part
When I use this in my service it looks like this:
function getUsers(date, search) {
return $http.get('/api/users/get', {
params: {
date: UtilsService.formatDate(date),
search: search
}
})
.then(getData)
.catch(handleErr);
function getData(response) {
return response.data;
}
function handleErr(err) {
LoggerService.error('Could not retrieve users.', err ,'Ooops');
}
}
Knowing the server will return an http status code 500, I thought it would go right to the catch block. But it also returns the data /which is undefined in the then block
I use my service in my controller like this:
function getUsers(date, search) {
isAdmin();
vm.loading = true;
vm.filteredUsers = [];
return UsersService.getUsers(date, search).then(function(data) {
vm.loading = false;
allUsers = data || [];
vm.filteredUsers = allUsers.slice(0, 50);
vm.distribution = UsersService.getDistribution(allUsers);
return vm.filteredUsers;
});
}
My problem is, since the then part is triggered in my service. I'm trying to slice undefined
My question is: What are som best practices when it comes to this sort of pattern.
The problem is that your catching the error from your API and then returning the promise created by .catch.
Quick example
promise.then(function(data) {
throw 'Some error';
}).catch(function (err) {
console.log(err) // will output 'Some error'
}).then(function () {
// This will run even though we have a catch before
});
So how can we prevent the .then it's easy we throw an error inside the .catch
promise.then(function(data) {
throw 'Some error';
}).catch(function (err) {
console.log(err) // will output 'Some error'
throw 'You shall not pass'
}).then(function () {
// This will not run
});
So in your case you have two options, one throw an error as I said or two inject the $q service into your service:
function getUsers(date, search) {
return $http.get('/api/users/get', {
params: {
date: UtilsService.formatDate(date),
search: search
}
})
.then(getData)
.catch(handleErr);
function getData(response) {
return response.data;
}
function handleErr(err) {
LoggerService.error('Could not retrieve users.', err ,'Ooops');
return $q.reject(err);
}
}
You could do something like that
function getUsers(date, search, cb) {
return $http.get('/api/users/get', {
params: {
date: UtilsService.formatDate(date),
search: search
}
})
.then(cb)
.catch(handleErr);
function handleErr(err) {
LoggerService.error('Could not retrieve users.', err ,'Ooops');
}
}
And then in your controller
UsersService.getUsers(date, search, function(data) {
vm.loading = false;
allUsers = data || [];
vm.filteredUsers = allUsers.slice(0, 50);
vm.distribution = UsersService.getDistribution(allUsers);
});