Async-WaterFall not working as expected - javascript

waterfall function with two calls but the second on is not waiting for the first one to completely finish. The first one has a mongodb.find() call in it.
Here is the async-waterfall function
app.get("/news", function(req, res) {
async.waterfall([
function (callback) {
var blogs = tendigiEngine.getAllBlogs(callback);
callback(null, blogs);
},
function (blogs, callback) {
var array = tendigiEngine.seperateBlogs(blogs, callback);
callback(null, array );
}
], function (err, result) {
// result now equals 'done'
console.log("done");
console.log(result);
});
});
Here are the two functions being called:
getAllBlogs():
exports.getAllBlogs = function() {
Blog.find(function(err, theBlogs){
if(!err) {
return theBlogs;
}
else {
throw err;
}
});
}
seperateBlogs():
exports.seperateBlogs = function(blogs) {
if(blogs.length === 0 ) {
return 0;
}
else {
blogs.reverse();
var blog = blogs[0];
blogs.shift();
var finArray = [blog, blogs];
return finArray;
}
console.log("asdf");
}
It is important that seperateBlogs won't be called before getAllBlogs() has returned theBlogs, but it is being called before the value is returned. I used Async_Waterfall to avoid this problem but it keeps recurring, which means I am using it wrong. What am I doing wrong here and how can I fix it?
Thanks!

Your exported functions are missing the callback parameters:
exports.getAllBlogs = function(cb) {
Blog.find(cb);
};
exports.seperateBlogs = function(blogs, cb) {
if (blogs.length === 0 )
return cb(null, blogs);
blogs.reverse();
var blog = blogs[0];
blogs.shift();
cb(null, [blog, blogs]);
}
Then your main code can be simplified as well:
async.waterfall([
tendigiEngine.getAllBlogs,
tendigiEngine.seperateBlogs
], function (err, result) {
// result now equals 'done'
console.log("done");
console.log(result);
});

Related

undefined var module.exports

For some reason, I can't get values returned from module.exports function from a separate custom module. I tried many ways from many sources from >10s researched posts. If you want to vote down, please read my bio or if you want to help I will be happy to accept your answer.
// restapi/index.js
module.exports = function gifs() {
giphy.search('Pokemon', function (err, res) {
return res.data[0];
});
}
// main server.js
var readapi = require('restapi')
console.log(readapi.gifs());
// Output:__________________
TypeError: readapi.gifs is not a function
You are exporting a function, not an object with a function and you are using a sync function (console.log) with an async operation.. it won't work.
You need to write it like this:
module.exports = function gifs(cb) {
giphy.search('Pokemon', function (err, res) {
if(err) { cb(err) }
else { cb(null, res.data[0]) }
});
}
----
var readapi = require('restapi')
readapi((err, data) => { console.log({err, data}) })
Remember the difference between:
module.export = {
hello: () => { console.log('world') }
}
// usage: require('./hello').hello()
module.export = () => { console.log('world') }
// usage: require('./hello')()
Try this code
module.exports.gifs = function gifs() {
return new Promise((resolve, reject) => {
giphy.search('Pokemon', function (err, res) {
if (err) reject(err);
else resolve(res.data[0]);
});
});
}
// main server.js
var readapi = require('restapi')
readapi.gifs().then(console.log);

Generating functions for async waterfall with Map

I am trying to produce async function by iterating through the array of 'titles' and passing array of function later to async waterfall.
according to docs:
var asyncFunction = [
function(callback){
asyncFunc(1, function(){
callback(null);
});
},
// page 2
function(data, callback){
asyncFunc(2, function(){
callback(null, data);
});
}
]
The first function takes callback as the first parameter, while the second and all subsequent functions function is taking data as the first parameter and callback as the second.
My question how do I create function conditionally based on the index of map iteration?
Below is !non-working code example to give you an idea what I'm trying to accomplish.
Thanks
const asyncFuncs = ['a','b','c'].map( (letter, index) => {
const args = i == 0 ? [callback] : [data, callback]
return function(args...){
asyncFunc(2, function(){
callback(null, data);
});
}
})
async.waterfall(asyncFuncs, (error, result) => {})
Sorry if I was wrong.
I guess that you want to dynamic waterfall functions base on your data.
var yourData = ['a','b','c'];
async.map(yourData, yourAssignFunction, function (err, result) {
if(!err) {
console.log('Success: ' + result);
} else {
console.log('Error: ' + err);
}});
function yourAssignFunction(item, callback) {
if (item === 'a') { // your conditions check here
// use waterfall 1
callback(waterfallFunction1);
} else {
// use waterfall 2
callback(waterfallFunction2);
}
}
function waterfallFunction1(item, callback) {
async.waterfall([
function(cb){
console.log(' -> task1: ', item);
cb(null, item);
},
function(response,cb){
console.log(' -> task2: ', item);
cb(null, item);
}], callback)
}

Converting to async.waterfall

I have following code
function someHelper(someList) {
return someList.map((item) => {
return (next) => someService.firstCall(paramForFirst, (err, result) => {
if(err) next(err);
else someService.secondCall(paramForSecond, result, next);
})
});
}
module.exports = {
doSomething(param, callback) {
async.parallel(someHelper(someList), callback);
}
};
How I can convert second return of someHelper function to use async.waterfall?
Here is the solution I have. Pass the next(callback) from parallel and then call methods in waterfall array list and finally call the next at the end. By defination waterfall will call callback with result or error.
function someHelper(someList) {
return someList.map((item) => {
return (next) => {
async.waterfall([
async.apply(someService.firstCall, paramForFirst),
async.apply(someService.secondCall, paramForSecond)
], next);
}
});
}

Collect asynchronous and synchronous data in loop

var data = [10,21,33,40,50,69];
var i = 0;
var dataSeq = [];
while(i<data.length){
if(data[i]%2 == 0){
store.findOne({'visibility': true},function(err, data){
dataSeq.push(i)
i++;
});
}
else{
dataSeq.push(i)
i++;
}
}
if(i==data.length-1){
console.log(dataSeq) // Should Print [1,2,3,4,5]
return res.status(200).send({ message: 'Task Completed'})
}
I want to collect data as per loop excecutes.
I am aware about how to handle async calls in nodejs. But I want the callbacks in sequence.
e.g. Though there is a async call in if condition i want to hault the loop, so that I can push value of i in dataSeq and it will result in [1,2,3,4,5] array. I want that sequence because my post operations are dependent on that sequence.
I think asyncjs#eachSeries has what you need.
Your code would become something like this:
async.each(data, (item, callback) => {
if(item%2 == 0){
store.findOne({'visibility': true},function(err, data){
dataSeq.push(i)
i++;
});
}
else{
dataSeq.push(i)
i++;
}
}, (err) => {
// if any of the callbacks produced an error, err would equal that error
});
You can use something like async#eachOf
var async = require('async');
var data = [10,21,33,40,50,69];
var dataSeq = [];
async.eachOf(data, function(value, key, cb) {
if (value % 2 == 0) {
store.findOne({ 'visibility': true })
.then(function(doc) {
dataSeq.push(key);
})
.catch(function(err) {
return cb(err);
});
} else {
cb();
}
}, function(err) {
if (err) {
console.error(err)
return res.status(500).send(); # handle the error as you want
}
return res.status(200).send({ message: 'Task Completed'})
})

Q.js: How can I rewrite an async series flow in Q.js?

In an attempt to grasp Q.js, I'd like to convert the following code using async.series in Q.js. Basically I create a folder if it doesn't exist (using mkdirp), move a file into a backup folder and save a file into a main folder.
var async = require('async');
var fs = require('fs');
var path = require('path');
var sessiondId = new Date().getTime() % 2 == 0 ? new Date().getTime().toString() : '_1234';
var backupFolder = path.join(__dirname,sessiondId);
var backupFullPath = path.join(backupFolder,'a.txt');
var fullPath = path.join(__dirname,'main','a.txt');
var mkdirp = require('mkdirp');
async.series({
createOrSkip: function(callback) {
mkdirp(backupFolder, function (err, dir) {
if(err) {
callback(err, null);
} else {
callback(null, {created: !!dir, folderAt: backupFolder});
}
});
},
move: function(callback) {
fs.rename(fullPath, backupFullPath, function(err) {
if(err) {
callback(err, null);
} else {
callback(null, {backupAt: backupFullPath});
}
});
},
write: function(callback) {
fs.writeFile(fullPath, 'abc', function(err) {
if (err) {
callback(err, null);
} else {
callback(null, {saveAt: fullPath});
}
});
}
}, function(err, result) {
console.log(result);
});
Actually I don't know where to start. Thanks for your help.
R.
The key is to convert the node.js functions to return promises using Q.denodeify before you start, this means the header of your file should look like:
var Q = require('q')
var fs = require('fs');
var path = require('path');
var sessiondId = new Date().getTime() % 2 == 0 ? new Date().getTime().toString() : '_1234';
var backupFolder = path.join(__dirname,sessiondId);
var backupFullPath = path.join(backupFolder,'a.txt');
var fullPath = path.join(__dirname,'main','a.txt');
var mkdirp = Q.denodeify(require('mkdirp'));
var rename = Q.denodeify(fs.rename);
var writeFile = Q.denodeify(fs.writeFile);
That change wouldn't be needed if node.js natively supported promises.
Option 1
// createOrSkip
mkdirp(backupFolder)
.then(function (dir) {
// move
return rename(fullPath, backupFullPath);
})
.then(function () {
// write
return writeFile(fullPath, 'abc');
})
.done(function () {
console.log('operation complete')
});
I don't think it gets much simpler than that. Like #Bergi said though, it's more similar to "waterfall". If you want the exact behavior of series (but with promises) you'll have to use something like Option 2 or Option 3.
Option 2
You could write out the code manually to save the results. I usually find that, although this requires a little extra writing, it's by far the easiest to read:
var result = {}
mkdirp(backupFolder)
.then(function (dir) {
result.createOrSkip = {created: !!dir, folderAt: backupFolder};
return rename(fullPath, backupFullPath);
})
.then(function () {
result.move = {backupAt: backupFullPath};
return writeFile(fullPath, 'abc');
})
.then(function () {
result.write = {saveAt: fullPath};
return result;
})
.done(function (result) {
console.log(result);
});
Option 3
If you find yourself using this sort of code all the time, you could write a very simple series helper (I've never found the need to do this personally):
function promiseSeries(series) {
var ready = Q(null);
var result = {};
Object.keys(series)
.forEach(function (key) {
ready = ready.then(function () {
return series[key]();
}).then(function (res) {
result[key] = res;
});
});
return ready.then(function () {
return result;
});
}
promiseSeries({
createOrSkip: function () {
return mkdirp(backupFolder).then(function (dir) {
return {created: !!dir, folderAt: backupFolder};
});
},
move: function () {
return rename(fullPath, backupFullPath)
.thenResolve({backupAt: backupFullPath});
},
write: function () {
return writeFile(fullPath, 'abc')
.thenResolve({saveAt: fullPath});
}
}).done(function (result) {
console.log(result);
});
I'd say once you've written the helper, the code is a lot clearer for promises than with all the error handling cruft required to work with callbacks. I'd say it's clearer still when you either write it by hand or don't keep track of all those intermediate results.
Summing Up
You may or may not think these examples are clearer than the async.series version. Consider how well you might know that function though. It's actually doing something pretty complex in a very opaque manner. I initially assumed that only the last result would be returned (ala waterfall) and had to look it up in the documentation of Async. I almost never have to look something up int the documentation of a Promise library.
Make each of your functions return a promise. Construct them with a Deferred:
function createOrSkip(folder) {
var deferred = Q.defer();
mkdirp(folder, function (err, dir) {
if(err) {
deferred.reject(err);
} else {
deferred.resolve({created: !!dir, folderAt: backupFolder});
}
});
return deferred.promise;
}
However, there are helper functions for node-style callbacks so that you don't need to check for the err yourself everytime. With Q.nfcall it becomes
function createOrSkip(folder) {
return Q.nfcall(mkdirp, folder).then(function transform(dir) {
return {created: !!dir, folderAt: backupFolder};
});
}
The transform function will map the result (dir) to the object you expect.
If you have done this for all your functions, you can chain them with then:
createOrSkip(backupfolder).then(function(createResult) {
return move(fullPath, backupFullPath);
}).then(function(moveResult) {
return write(fullPath, 'abc');
}).then(function(writeResult) {
console.log("I'm done");
}, function(err) {
console.error("Something has failed:", err);
});
Notice that this works like async's waterfall, not series, i.e. the intermediate results will be lost. To achieve that, you would need to nest them:
createOrSkip(backupfolder).then(function(createResult) {
return move(fullPath, backupFullPath).then(function(moveResult) {
return write(fullPath, 'abc');.then(function(writeResult) {
return {
createOrSkip: createResult,
move: moveResult,
write: writeResult
};
});
});
}).then(function(res){
console.log(res);
}, function(err) {
console.error("Something has failed:", err);
});

Categories

Resources