Async.forEach call the callback - javascript

How to call the foreach callback after complete the series?
My code:
async.forEach(rowsIni, function (row, callback2) {
strQuery = "SELECT ...";
DB.query(strQuery, row, function (err, rows) {
if (err) {
console.log("SQL Error(SELECT) > " + err.message);
} else {
async.series([
function (callback) {
async.parallel([
function (callback) {
callback();
},
function (callback) {
callback();
}
], function (err) {
callback();
});
},
function (callback) {
callback();
}
], function() {
callback2
});
}
});
}, function (err) {
if (err) {
console.log("ASYNC Error > " + err.message);
}
console.log('END OF FOREACH');
callback(otherFunction);
});
How to call the callback2? If I call thus, the callback2 is not called.

async.series([...], function() {
callback2 // does nothing
})
You don't actually call callback2. Either use callback2() (call it) or
async.series([...], callback2);
To just pass the callback function.
edit:
async.eachSeries(rowsIni, function (row, callback2) {
...
async.series([
function (callback) {
async.parallel([
... functions
], callback);
},
function (callback) {
callback();
}
], callback2);
});

Related

Unit testing async function with callback

Trying to write unit tests for reading a json file with the readFile function however I get the error: Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout. I must be doing something wrong when mocking the json file.
The function:
function jsonReader(filePath, cb) {
fs.readFile(filePath, (err, fileData) => {
if (err) {
return cb && cb(err);
}
try {
const object = JSON.parse(fileData);
return object;
} catch (err) {
return cb && cb(err);
}
});
}
module.exports = jsonReader;
Then the testfile:
const jsonReader = require('.././ReadJson');
jest.mock('fs', () => {
const MOCK_FILE_INFO = { 'test.json': JSON.stringify({ name: 'myname' }) };
return {
readFile: (fpath, opts) => {
if (fpath in MOCK_FILE_INFO) {
return MOCK_FILE_INFO[fpath];
}
}
};
});
test('Test file', (done) => {
function callback(data) {
expect(data.name).toBe('myname');
done();
}
jsonReader('test.json', callback);
});
I tried to change the timeout but if I put it higher the execution also takes longer and it's still giving the same error.
You're trying to use your functions synchronously?
jest.mock('fs', () => {
const MOCK_FILE_INFO = { 'test.json': JSON.stringify({ name: 'myname' }) };
return {
readFile: (fpath, callback) => {
if (fpath in MOCK_FILE_INFO) {
callback(null, MOCK_FILE_INFO[fpath]);
}
}
};
});
function jsonReader(filePath, cb) {
fs.readFile(filePath, (err, fileData) => {
if (err) {
return cb && cb(err);
}
try {
const object = JSON.parse(fileData);
cb(object);
} catch (err) {
return cb && cb(err);
}
});
}
module.exports = jsonReader;

Callback function getting called before initial function finishes

I am having issues with callback functions - I have a main function which has two callback functions inside. This is the main function
socket.on('play next video', function(data) {
removeVideo(cue[0], getCueFromDb(function() {
io.sockets.emit('next video');
}));
});
My removeVideo function looks like this:
function removeVideo(id, callback) {
Video.find({'id' : id}).remove(function(err, data) {
if (err)
console.log(err)
console.log("Removed video", id)
});
if (callback)
callback();
else
return
}
and the getCueFromDb function looks like this
function getCueFromDb(callback) {
Video.find({}).exec(function(err, videos) {
if (err) {
console.log(err)
}
if (videos.length) {
cue.length = 0 // empty array
videos.forEach(function(video) {
cue.push(video.id) // push all the videos from db into cue array
});
io.sockets.emit('send cue', {cue: cue});
}
else {
console.log("No more videos in database!")
}
if (callback)
callback();
else
return
});
}
However the functions aren't getting called in the correct order - am I doing something wrong?
Your callback needs to be inside your find.remove()
function removeVideo(id, callback) {
Video.find({'id' : id}).remove(function(err, data) {
if (err)
console.log(err)
console.log("Removed video", id)
if (callback)
callback();
else
return
});
You have to change removeVideo so callback we be called only after delete.
That's the code:
function removeVideo(id, callback) {
Video.find({'id' : id}).remove(function(err, data) {
if (err)
console.log(err)
console.log("Removed video", id)
if (callback)
callback();
else
return
}
});
So callback will be called only after vide is really removed.

can I use async.waterfall inside async.parallel?

I want to call two functions and get the results in parallel but one of the function's results needed to be adapted. So the function structure is:
function test(cb) {
async.parallel({
func1: function foo(cb1) {
cb(null, {});
},
func2: function bar(cb2) {
async.waterfall([
function adapt1(next) {
//do something;
},
function adapt2(next) {
//do something;
}
], function handler(err, res) {
//do something.
})
}
}, function handler2(err, res) {
cb(null, {});
})
}
However, it just seems hang there forever. not sure if I can use async in this way....
Sure you can! You have to be sure to call your callbacks in the correct order and in the first place. For example, func1 should be calling cb1 not cb. Secondly, your waterfall is not invoking their callbacks at all.
Take this code for example.
'use strict';
let async = require('async');
function test(callback) {
async.parallel({
func1: function(cb) {
cb(null, { foo: 'bar' });
},
func2: function(cb) {
async.waterfall([
function(cb2) {
cb2(null, 'a');
},
function(prev, cb2) {
cb2(null, 'b');
}
], function(err, result) {
cb(err, result);
});
}
}, function(err, results) {
callback(err, results);
});
}
test(function(err, result) {
console.log('callback:', err, result);
});
Outputs: callback: null { func1: { foo: 'bar' }, func2: 'b' }

Async series's callback doesn't fire

My problem here is that when I use this code I always get the callback in ensureAppIsValid and the one in the Async series seems to be never fired
var ReviewProcess = function (args) {
'use strict';
assert(args.application, 'Need an application to review');
this.app = args.application;
};
ReviewProcess.prototype.ensureAppIsValid = function (callback) {
'use strict';
if (this.app.isValid()) {
callback(null, this.app);
} else {
callback(this.app.validationMessage(), null);
}
};
ReviewProcess.prototype.processApplication = function (callback) {
'use strict';
async.series([
this.ensureAppIsValid(callback)
], function (err, callback) {
if (err) {
return callback(null, {
success: false,
message: err
});
}
callback(null, {
success: true,
message: 'Welcome to Mars'
});
});
};
It looks like you're using the word 'callback' too many times and the code isn't doing what you're expecting it to. You are passing the top level callback into the ensureAppIsValid() function, so once that function executes it doesn't go to async's callback. It also looks like you don't need the extra callback in async's follow up.
How about this:
ReviewProcess.prototype.processApplication = function (callback) {
'use strict';
async.series([
this.ensureAppIsValid(cb)
], function (err) {
if (err) {
return callback(null, {
success: false,
message: err
});
}
callback(null, {
success: true,
message: 'Welcome to Mars'
});
});
};
Async series requires a lists of functions as tasks to be ran.
You pass
this.ensureAppIsValid(callback)
But this is the call of the function, not the function itself.
Try this:
Async.series([
this.ensureAppIsValid.bind.apply(this.ensureAppIsValid, [null, [callback]])
], ... )
You shouldn't pass argument callback to this.ensureAppIsValid(). Instead, use here local callback parameter. For example, named cb.
Try:
var ReviewProcess = function (args) {
'use strict';
assert(args.application, 'Need an application to review');
this.app = args.application;
};
ReviewProcess.prototype.ensureAppIsValid = function (callback) {
'use strict';
if (this.app.isValid()) {
callback(null, this.app);
} else {
callback(this.app.validationMessage(), null);
}
};
ReviewProcess.prototype.processApplication = function (callback) {
'use strict';
async.series([
this.ensureAppIsValid(cb)
], function (err, callback) {
callback(null, {
success: !err,
message: err? err : 'Welcome to Mars'
});
}
});
};
I have also slightly changed your eventual callback in async.series. Now it's more compact.

Retry an async function if the error is retryable

How can I change my logic to retry if the err.retryable = true in the following code:
async.each(queues, function (queue, callback) {
sqs.getQueueUrl({'QueueName': queue.queue}, function (err, qurl) {
if (err) {
if (err.retryable) {
// How to retry sqs.getQueueUrl({'QueueName': queue.queue}...?
} else {
console.error(err, err.stack);
callback(err);
}
}else{
//Do lots of things here
}
})
}, function (err) {
//...
})
In addition to the advice by dfsq to name your callback and use it an asynchronously recursive manner, see also async.retry from the async module by Caolan McMahon. Example:
async.retry(3, apiMethod, function(err, result) {
// do something with the result
});
More complex example:
async.auto(
{
users: api.getUsers.bind(api),
payments: async.retry(3, api.getPayments.bind(api))
}, function(err, results) {
// do something with the results
}
);
More details in the docs.
UPDATE
A better solution for your use case:
I wrote a utility function that you can use to make your original method support any number of retries (with err.retryable support).
You can use it this way:
var retryingFunction = withRetries(sqs, sqs.getQueueUrl);
(Note that you need to provide both sqs and sqs.getQueueUrl)
And now you can use the retryingFunction just like you would use sqs.getQueueUrl but with a number of retries as the first arguments. The retries will only be done when err.retryable is true.
So now, instead of:
sqs.getQueueUrl({'QueueName': queue.queue}, function (err, qurl) {
// ...
});
you can use:
retryingFunction(3, {'QueueName': queue.queue}, function (err, qurl) {
// ...
});
where 3 is the number of retries.
And this is the function that I wrote to make the above possible:
function withRetries(obj, method) {
if (!method) {
method = obj;
obj = null;
}
if (typeof method != "function") {
throw "Bad arguments to function withRetries";
}
var retFunc = function() {
var args = Array.prototype.slice.call(arguments);
var retries = args.shift();
var callback = args.pop();
if (typeof retries != "number" || typeof callback != "function") {
throw "Bad arguments to function returned by withRetries";
}
var retryCallback = function (err, result) {
if (err && err.retryable && retries > 0) {
retries--;
method.apply(obj, args);
} else {
callback(err, result);
}
};
args.push(retryCallback);
method.apply(obj, args);
};
return retFunc;
}
See this LIVE DEMO to play with it and see how it works.
It works fine in the demo, I hope it will also work for your code.
You can give queue callback a name and provide it in retry request again. Try this:
async.each(queues, function (queue, callback) {
sqs.getQueueUrl({'QueueName': queue.queue}, function queueCallback(err, qurl) {
if (err) {
if (err.retryable) {
sqs.getQueueUrl({'QueueName': queue.queue}, queueCallback);
} else {
console.error(err, err.stack);
callback(err);
}
} else {
//Do lots of things here
}
});
}, function (err) {
//...
});

Categories