I have a callback function that returns an object from the database. However, in my async.waterfall the function 'external' does not wait for the object to be fully loaded meaning it is undefined when passed in. This means my final error is TypeError: Cannot read property 'replace' of undefined. What am I doing wrong?
function loadModelInstance (name, callback) {
Model.findOne({ name: name }, function (_err, result) {
if (result) {
return callback(_err, result.content)
}
})
}
function generatedNow (modelInstance) {
generatedKeys = generatedKeys.concat(getAllMatches(generatedRegexp, modelInstance.replace(/(\n|\r)/g, '')));
}
async.waterfall(
[
function loadTemplate (wfaCallback) {
loadModelInstance(name, function (_err, modelInstance) {
wfaCallback(_err, modelInstance)
})
},
function external (modelInstance, wfaCallback) {
generatedNow(tracking, message, modelInstance, placeholders, function (err, updatedPlaceholders) {
})
},
],
function (err) {
// Node.js and JavaScript Rock!
}
);
Could you please provide more details. where are you calling "generateNow" function. i don't see function call for "generateNow".
Looks like you haven't used the parameter order properly. Below code should work.
async.waterfall(
[
function loadTemplate(wfaCallback) {
loadModelInstance(name, function(_err, modelInstance) {
wfaCallback(_err, modelInstance);
});
},
function external(err, modelInstance, wfaCallback) {
generatedNow(modelInstance, tracking, message, placeholders, function(
err,
updatedPlaceholders
) {});
}
],
function(err) {
// Node.js and JavaScript Rock!
}
);
Related
I want to send request to a mongodb database.
For example I have this object:
{
id:"1",
requestType : {
"api1" : {count:12,firstTime:12},
"api2" : {count:6,firstTime:18}
}
}
after getting data by "id" I want to append another row to "requestType" for example "api3":{count:56,firstTime:11}.
my expected object is:
{
id:"1",
requestType : {
"api1" : {count:12,firstTime:12},
"api2" : {count:6,firstTime:18},
"api3":{count:56,firstTime:11}
}
}
currently I'm using this query by mongoose:
apiAttemptsModel.findOneAndUpdate(query, {
$set: {
requestType : {"api3":{count:56,firstTime:11}}
}
}, {upsert: true, new: true}, function (err, row) {
if (err) {
callback('err is ' + err)
} else {
callback(row);
}
});
But this code will exchange old requestType object with the new one.
Try this:
apiAttemptsModel.findOneAndUpdate(query, {
$set: {
"requestType.api3" : {count:56, firstTime:11}
}
}, {upsert: true, new: true}, function (err, row) {
if (err) {
callback('err is ' + err)
} else {
callback(row);
}
});
By the way, this is not the proper way to use callbacks.
callbacks in node are usually called with the following signature:
function(error, data) -
thus when calling callback(row); a user of this method might expect an error there, much like you did with the callback function on line 5.
The first argument is usually null when no error has occurred.
In addition, calling callback('err is ' + err) will only keep the error's message and discard its stack trace, because the error is "converted" to a string.
I'd convert your code to:
apiAttemptsModel.findOneAndUpdate(query, {
$set: {
"requestType.api3" : {count:56, firstTime:11}
}
}, {upsert: true, new: true}, function (err, row) {
if (err) {
callback(err)
} else {
callback(null, row);
}
});
Set upset to false. This will update the document, but will not create it if it does not already exist.
https://docs.mongodb.com/manual/reference/method/db.collection.update/
Code:
let parseCarList = [];
function addNewCars(req, res) {
parseRequestAndCreateCarList(req.body)
.then(function () {
if (parseCarList.length != 0) {
res.status(200).send('OK');
}
}, function (err) {
res.status(200).send(err);
});
}
function parseRequestAndCreateCarList(data) {
return carListParsing(data);
}
function carListParsing(data, parentId = null) {
//this function don't return anything
//It parse req.body and fill the parseCarList array
//I can't return anything because I use recursion
parseCarList.push({
car_company: data.car_company,
parent_id: parentId
});
if (data.daughters) {
data.daughters.forEach(item => {
carListParsing(item, data.car_company);
});
}
}
My function carListParsing is to do array:
[ { car_company: 'vw', parent_id: null },
{ car_company: 'seat', parent_id: 'vw' }]
But I want to be sure, that it function will not block my code.
I get error "Cannot read property 'then' of undefined". Is my function parseRequestAndCreateCarList return the promise that it has been fulfilled or not?
Why in this case the 'then' is undefined?
P.S. Is there a way to leave carListParsing without wrapper parseRequestAndCreateCarList?
You can only use then on a returned promise, so you need to change
function parseRequestAndCreateCarList(data) {
return carListParsing(data);
}
to
function parseRequestAndCreateCarList(data) {
return new Promise(function(resolve, reject) {
resolve(carListParsing(data))
});
}
I have the following array:
var files = [
{
name: 'myfile.txt'
},
{
name: 'myfile2.txt'
}
];
I'm using async for access this objects and send them to extraction, like this:
extraction function:
var extraction = function(file, callback) {
// extract
// return text
callback(text);
};
Using async
var fn = function(files, callback) {
Async.mapLimit(files, 3000, function(file, done) {
extraction(file, function(texts) {
done(texts);
});
}
}, function(texts) {
callback(texts);
}
How i need call:
fn(files, function(texts) {
console.log(texts); // new objects, now with text's
});
Like this, i'm receiving the following error:
Done was already called.
This happens because i can't call the done inside the async two ways, and i'm doing that.
To around this problem, i'm doing this inside the Async:
if (files.length === texts.length) {
done(texts);
}
So will compare. Files size is the same as texts size? Yes? So you can call the done.
This will work perfectly. The problem with this is that i can't pass a error argument inside a else statement, like this:
if (files.length === texts.length) {
done(null, texts);
} else {
done('ERROR'!);
}
First because would be call in every loop, so will cause the same error:
Done was already called.
And even if i could, would break the loop because will be called in the first looping.
How can i fix this? I need a light here :/
This code is not braced correctly and the callback arguments were not in the proper position in a couple of places:
var fn = function(files, callback) {
Async.mapLimit(files, 3000, function(file, done) {
extraction(file, function(texts) {
done(texts);
});
}
}, function(texts) {
callback(texts);
}
It should be:
var fn = function (files, callback) {
Async.mapLimit(files, 3000, function (file, done) {
extraction(file, function (texts) {
done(null, texts);
});
}, function (err, texts) {
callback(texts);
});
}
Async.mapLimit() takes four arguments. You were not passing the fourth argument correctly. In fact, if you paste your code into http://jshint.com/, it will show you where the errors are (frankly I'm surprised that the code even ran).
Here's an article that shows you some Async.mapLimit() coding examples that work just like you are trying to do: Concurrency level (async.mapLimit)
I am having difficulties with Javascript's asynchronous functions.
Code displayed below is so far almost entire code I have and I can't get it to work.
I ma trying to use Eventful API to pull some data from the server, and display it in my frontend which is created by jQuery.
So, the problem is following: function search, which is calling function Eventful.prototype.searchanje always ends up with undefined value, while a few seconds later, function searchanje console logs actual/recieved data.
I am fairly new to jQuery, and I was wondering if there is any kind of "template" for handling these things, concretely for waiting until the function returns value and then proceeding with next operations.
So far I have tried using deferred and promises, and read quite a lot tutorials and stackoverflow answers on similar subjects, but I can't get it to work properly.
Or, if deferred and promises are the right way to go, could you show me the way it is supposed to be done?
Thanks in advance
'use strict';
function Eventful(_name) {
var name = _name;
var appKey = 'appKey';
var that = this;
return {
getName: function() {
return name;
},
getAppKey: function() {
return appKey;
},
search: function() {
that.searchanje(appKey).then(function(oData) {
console.log('oData');
});
}
};
}
Eventful.prototype.searchanje = function(appKey) {
var oArgs = {
app_key: appKey,
q: 'sport',
where: 'Zagreb',
date: '2013061000-2015062000',
page_size: 5,
sort_order: 'popularity',
};
EVDB.API.call('/events/search', oArgs, function(oData) {
console.log(oData);
return oData();
});
};
On the line
EVDB.API.call('/events/search', oArgs, function(oData) {
you are passing a CALLBACK function. This function (the one that starts function(oData)) is not executed immediately. It is executed asynchronously, when the result of your API call is returned.
Try putting console.log() statements around your code, and watch the order they appear in the console. For example:
function Eventful(_name) {
var name = _name;
var appKey = 'appKey';
var that = this;
return {
getName: function() {
return name;
},
getAppKey: function() {
return appKey;
},
search: function() {
console.log('Search function called');
that.searchanje(appKey).then(function(oData) {
console.log('searchanje returned with data:');
console.log('oData');
});
}
};
}
Eventful.prototype.searchanje = function(appKey) {
console.log('function searchanje being called with appKey: ', appKey);
var oArgs = {
app_key: appKey,
q: 'sport',
where: 'Zagreb',
date: '2013061000-2015062000',
page_size: 5,
sort_order: 'popularity',
};
console.log('Calling EVDB.API.call');
EVDB.API.call('/events/search', oArgs, function(oData) {
console.log('EVDB.API callback executing with data:');
console.log(oData);
return oData();
});
console.log('finished calling EVDB.API.call');
};
I wonder if a better way ahead might be to "promisify" EVDB.API.call().
(function(app_key) {
var appKeyObj = { 'app_key': app_key },
EVDB.API.callAsync = function(path, params) {
return $.Deferred(function(dfrd) {
EVDB.API.call(path, $.extend(appKeyObj, params), function(oData) {
if (oData.error === "1") {
//translate oData.status and oData.description into a javascript Error object
// with standard .name and .message properties.
err = new Error(oData.description);
err.name = oData.status;
dfrd.reject(err);
} else {
dfrd.resolve(oData);
}
});
});
});
})('myAppKey'); //hard-coded app key
Notes:
The global namespace is avoided by :
using a self-executing anonymous wrapper
extending the EVDB.API namespace.
The "Async" suffix for a promisified method has a precendent in bluebird
From the little I understand of EVDB.API, EVDB.API.call() does just about everything, hence the single method EVDB.API.callAsync(). If necessary, further async methods could be added to EVDB.API
You might choose to use a dedicated promise lib such as bluebird or when.js in place of jQuery, In addition to including the lib, mods to the above code would be minor.
Now, instead of calling EVDB.API.call(path, params, callback) you would call EVDB.API.callAsync(path, params) to be returned a Promise.
var params = {
//app_key will be inserted automatically
q: 'sport',
where: 'Zagreb',
date: '2013061000-2015062000',
page_size: 5,
sort_order: 'popularity'
};
EVDB.API.callAsync('/events/search', params).then(function(oData) {
console.log(oData);
}, function(err) {
console.error(err);
});
I have a chain of function calls and use async.waterfall. It works like a charm. But I'd like to do it with jQuery Deferred. How to transform my code?
The example from jQuery site is like this. Both results are passed to done function:
$.when( $.ajax( "/page1.php" ), $.ajax( "/page2.php" ) ).done(function( a1, a2 ) {
// a1 and a2 are arguments resolved for the page1 and page2 ajax requests, respectively.
// Each argument is an array with the following structure: [ data, statusText, jqXHR ]
var data = a1[ 0 ] + a2[ 0 ]; // a1[ 0 ] = "Whip", a2[ 0 ] = " It"
if ( /Whip It/.test( data ) ) {
alert( "We got what we came for!" );
}
});
But my code is different. I need to pass a callback to every step of a waterfall and I have ifs in callbacks. How to implement it with jQuery? Is it possible?
async.waterfall([
function(cb) {
VK.Api.call('users.get', {user_ids: res.session.mid, fields: fields}, function(userDataRes) {
cb(null, userDataRes);
});
},
function(userDataRes, cb) {
if(userDataRes.response[0].city) {
VK.Api.call('database.getCitiesById', {city_ids: userDataRes.response[0].city}, function(cityDataRes) {
cb(null, userDataRes, {city: cityDataRes.response[0].name});
});
}
else {
cb(null, userDataRes, {});
}
},
function(userDataRes, cityDataRes, cb) {
if(userDataRes.response[0].country) {
VK.Api.call("database.getCountriesById", {country_ids: userDataRes.response[0].country}, function(countryDataRes) {
cb(null, userDataRes, cityDataRes, {country: countryDataRes.response[0].name});
});
}
else {
cb(null, userDataRes, {}, {});
}
},
function(userDataRes, cityDataRes, countryDataRes, cb) {
var resObj = $.extend(true, {}, userDataRes.response[0], cityDataRes, countryDataRes);
cb(null, resObj);
},
],
function(err, res) {
console.log("res::: ", res);
}
);
UPD 1:
So, I've implemented a solution, but it doesn't work as expected. There is an asynchronous API function call in .then() and jQuery deferred flow is broken there. I don't know how to make a .then() function as an API callback.
var dfr = $.Deferred();
dfr.then(function(val) {
// THIS is an asynchronous API function call. And its callback returns result that is passed to the next .then()
// But jQuery deferred flow doesn't follow this API call.
// It goes along to the next .then ignoring this API call.
// How to make it enter this API call and be returned from a API's callback.
VK.Api.call('users.get', {user_ids: res.session.mid, fields: fields}, function(userDataRes) {
// cb(null, userDataRes);
console.log("countryDataRes: ", userDataRes);
return userDataRes;
});
}).
then(function(userDataRes) {
console.log("countryDataRes: ", userDataRes);
if(userDataRes.response[0].city) {
VK.Api.call('database.getCitiesById', {city_ids: userDataRes.response[0].city}, function(cityDataRes) {
// cb(null, userDataRes, {city: cityDataRes.response[0].name});
return [userDataRes, {city: cityDataRes.response[0].name}];
});
}
else {
// cb(null, userDataRes, {});
return [userDataRes, {}];
}
}).
then(function(aRes) {
if(aRes[0].response[0].country) {
VK.Api.call("database.getCountriesById", {country_ids: aRes[0].response[0].country}, function(countryDataRes) {
// cb(null, userDataRes, cityDataRes, {country: countryDataRes.response[0].name});
return [aRes[0], aRes[1], {country: countryDataRes.response[0].name}];
});
}
else {
cb(null, aRes[0], {}, {});
}
}).
then(function(aRes) {
var resObj = $.extend(true, {}, aRes[0].response[0], aRes[1], aRes[2]);
console.log("cityDataRes: ", aRes[1]);
console.log("countryDataRes: ", aRes[2]);
cb(null, resObj);
return resObj;
}).
done(function(res) {
console.log("res::: ", res);
});
dfr.resolve();
Let's start with the general rule for using promises:
Every function that does something asynchronous must return a promise
Which functions are these in your case? Basically, the complete waterfall, each of the waterfall functions that took a cb and VK.Api.call.
Hm, VK.Api.call doesn't return a promise, and it's a library function so we cannot modify it. Rule 2 comes into play:
Create an immediate wrapper for every function that doesn't
In our case, it will look like this:
function callApi(method, data) {
var dfr = $.Deferred();
VK.Api.call(method, data, function(result) {
dfr.resolve(result);
});
// No error callbacks? That's scary!
// If it does offer one, call `dfr.reject(err)` from it
return dfr.promise();
}
Now we have only promises around, and do no more need any deferreds. Third rule comes into play:
Everything that does something with an async result goes into a .then callback
…and returns its result.
That result might as well be a promise not a plain value, .then can handle these - and will give us back a new promise for the eventual result of executing the "something". So, let's chain some then()s:
apiCall('users.get', {user_ids: res.session.mid, fields: fields})
.then(function(userDataRes) {
console.log("countryDataRes: ", userDataRes);
if (userDataRes.response[0].city) {
return apiCall('database.getCitiesById', {city_ids: userDataRes.response[0].city})
.then(function(cityDataRes) {
return [userDataRes, {city: cityDataRes.response[0].name}];
});
} else {
return [userDataRes, {}];
}
})
.then(function(aRes) {
if (aRes[0].response[0].country) {
return apiCall("database.getCountriesById", {country_ids: aRes[0].response[0].country})
.then(function(countryDataRes) {
return [aRes[0], aRes[1], {country: countryDataRes.response[0].name}];
});
} else {
return [aRes[0], aRes[1], {}];
}
})
.then(function(aRes) {
var resObj = $.extend(true, {}, aRes[0].response[0], aRes[1], aRes[2]);
console.log("cityDataRes: ", aRes[1]);
console.log("countryDataRes: ", aRes[2]);
return resObj;
})
.done(function(res) {
console.log("res::: ", res);
});
At least, that's what your original waterfall did. Let's polish it up a bit by executing getCitiesById and getCountriesById in parallel, and removing all the boilerplate of explicitly creating these aRes arrays.
function callApi(method, data) {
var dfr = $.Deferred();
VK.Api.call(method, data, function(result) {
dfr.resolve(result.response[0]);
// changed: ^^^^^^^^^^^^
});
// No error callbacks? That's scary!
// If it does offer one, call `dfr.reject(err)` from it
return dfr.promise();
}
apiCall('users.get', {user_ids: res.session.mid, fields: fields})
.then(function(userData) {
if (userData.city)
var cityProm = apiCall('database.getCitiesById', {city_ids: userData.city});
if (userData.country)
var countryProm = apiCall("database.getCountriesById", {country_ids: userData.country});
return $.when(cityProm, countrProm).then(function(city, country) {
var resObj = $.extend(true, {}, userData);
if (city)
resObj.city = city.name;
if (country)
resObj.country = country.name;
return resObj;
});
})
.done(function(res) {
console.log("res::: ", res);
});