Waiting on asynchronous functions within multiple conditionals - javascript

The meteor project I'm working on uploads files when certain conditions are satisfied. Regardless of whether or not the files are uploaded, a Meteor.call has to be made once the if statements have completed. Because of the conditionals, when I use callbacks it results in a lot of duplicate code. As it is written below, I expect the Meteor.call could be executed before the uploadFile callbacks get executed which would be a problem.
var data = {
name: "..."
//...
}
if(condition){
uploadFile(parameters, function(error,result){
if(err) handleError(err);
else data.url1 = result.secure_url;
}
if(condition2){
uploadFile(parameters, function(error,result){
if(err) handleError(err);
else data.url2 = result.secure_url;
}
/* This Meteor.call needs to wait until both if statements above
have completed */
Meteor.call('insertData', data, function(error,result){
//...
}

you could remove the conditionals and instead implement Javascript Promises they're fun and fancy plus they eliminate callback hell and provide a readable top down format:
http://jsfiddle.net/4v29u4do/1/
This fiddle shows how you could use promises to wait for async callbacks instead of conditional if statements
With promises you can return a new promise as the callback from a promise, and keep going and going and the callback will get passed to the new .then until you're done if there is any errors along the way, it skips right to the .catch.
function uploadFile(file) {
return new Promise(function(resolve, reject) {
// Simulate ASYNC Call for Uploading File
setTimeout(function() {
console.log(file + ' Uploaded Successfully!');
return resolve(file);
// if (err) {
// return reject(err);
// }
}, 3000);
});
}
var data = {};
uploadFile("filename1")
.then(function(cb) {
data.url1 = cb;
return uploadFile("filename2");
})
.then(function(cb) {
data.url2 = cb;
return uploadFile("filename5");
})
.then(function(cb) {
data.url3 = cb;
console.log(data);
//all done with callbacks
// Meteor.call("");
})
.catch(function(err) {
// One of the uploads failed log the err;
});

Related

Pass variables between Promises and functions

I have a problem.
I have to do two different SOAP calls to retrieve two list of vouchers and then use these lists to do a check on them and to do some job.
I put the two calls in different Promise functions because I want start the job on the lists after the call returned its result.
This is the first Promise call:
let vouchers = function(voucherTypeList){
return new Promise(function(resolve,reject){
const categoryId = "1000";
let args = {
"tns:CategoryId": categoryId
};
var header = {
"tns:Authenticate": {
"tns:UserName": soapVoucherWsdlUsername,
"tns:Password": soapVoucherWsdlPassword
}
};
// let voucherTypeList;
voucherClient.addSoapHeader(header);
voucherClient.GetVouchers(args, function(err, result) {
console.log("DENTRO GET VOUCHERS");
if (err) {
console.log(err);
writeResponse(res, '200', err);
} else {
//++++++++++++++++++++++
//voucherTypeList is what I want to return to the main function
voucherTypeList = mapGetVoucherTypeListResponse(result);
//++++++++++++++++++++++
}
resolve("done 1");
});
});
}
This is the second Promise call:
let issuedVouchers = function(accountId) {
return new Promise(function (resolve, reject) {
const categoryId = "1000";
let args = {
"tns:CategoryId": categoryId,
"tns:CheckRedeem": true,
"tns:IncludeRedeemed": false,
"tns:CardId": accountId
};
var header = {
"tns:Authenticate": {
"tns:UserName": soapVoucherWsdlUsername,
"tns:Password": soapVoucherWsdlPassword
}
};
let issuedVoucherList;
voucherClient.addSoapHeader(header);
voucherClient.GetVouchers(args, function (err, result) {
console.log("DENTRO GET ISSUED VOUCHERS");
if (err) {
console.log(err);
writeResponse(res, '200', err);
} else {
//++++++++++++++++++++++
//issuedTypeList is what I want to return to the main function
issuedTypeList = mapGetVoucherTypeListResponse(result);
//++++++++++++++++++++++
}
resolve("done 2");
});
});
}
And this is the main function, with the Promise flow:
function getAvailableVoucherTypes(req, res) {
var accountId = req.params.accountId;
vouchers(voucherTypeList).
then(issuedVouchers(accountId)).
then(function() {
//here I want to use voucherTypeList and issuedTypeList
//and do some jobs on them
console.log("OK");
});
}
How can I do this? I tried many solutions, but I'm not able to see voucherTypeList and issuedTypeList in the main function.
The then callbacks are getting the value of what you pass to the resolve function in your promises. You are currently passing arbitrary strings, which is useless... But for the demonstration, let's keep those and just log their values in your main script:
function getAvailableVoucherTypes(req, res) {
var accountId = req.params.accountId;
vouchers(voucherTypeList).
then(function(result){
console.log(result); //done 1
return issuedVouchers(accountId);
}).
then(function(result) {
console.log(result); //done 2
//here I want to use voucherTypeList and issuedTypeList
//and do some jobs on them
console.log("OK");
});
}
I'll let you play with your promises to pass the right variables...
Now, it seems that your 2 calls do not need to be sequential, so let's make them parallel, it's gonna be slightly easier for us too.
function getAvailableVoucherTypes(req, res) {
var accountId = req.params.accountId;
var promises = [vouchers(),issuedVouchers(accountId)]
Promise.all(promises).then(function(results){
//In Promise.all, the results of each promise are passed as array
//the order is the same as the order of the promises array.
var voucherTypeList = results[0];
var issuedTypeList = results[1];
});
}
BONUS: I do not want to complicate this task too much before you grasp it correctly. So I won't add more code. But note that you should use reject too, instead of handling your errors in every promise, you should reject them when things go wrong. Just reject(err) and add a second callback to your main script's then to handle any error that may happen. If you keep resolving your promises that did not work, you will not be passing the elements you are expecting and you'll need to add checks over every step.
Let's modify the GetVouchers callback to fit what I suggest.
voucherClient.GetVouchers(args, function (err, result) {
console.log("DENTRO GET ISSUED VOUCHERS");
if (err) {
reject(err);
} else {
resolve(mapGetVoucherTypeListResponse(result));
}
});
Once it is done on both your promises, we can change your main script to handle the error accordingly.
Promise.all(promises).then(function(results){
//Handle success like above.
},function(err){
//Handle error.
console.log(err.stack || err);
writeResponse(res, '200', err);
});

Directory check returning false in node.js

I'm having an issue with a Node.js function. I'm fairly certain that it is just an issue with the function being asynchronous but I want to be sure. I am checking to see if a certain path that was entered by the user is valid by checking if it exists.
var directoryExists = exports.directoryExists = function(filePath) {
return new Promise(function(resolve, reject) {
if (fs.statSync(filePath).isDirectory()){
resolve("Valid");
} else {
reject("Invalid");
}
});
}
These are my calls to the function:
files.directoryExists(sourcePath).then((msg) => {
console.log(msg);
}).catch(function(){
console.error("Promise Rejected");
});
files.directoryExists(destPath).then((msg) => {
console.log(msg);
}).catch(function(){
console.error("Promise Rejected");
});
I'm very new to the whole concept of asynchronous programming and promises so this is becoming quite frustrating. Any help would be appreciated.
It's not really the asynchronous thing that's catching you out, although there's a change you can make to improve that.
statSync can throw an exception (if the path doesn't match anything, for instance); you're not handling that, and so when it throws, that gets converted into a rejection. If you looked at the argument you're getting in your catch handler, you'd see the exception that it raises.
The async improvement is that since you're using a Promise, there's no reason to use statSync. Just use stat so you don't tie up the JavaScript thread unnecessarily.
So something along the lines of:
var directoryExists = exports.directoryExists = function(filePath) {
return new Promise(function(resolve, reject) {
// Make the request asynchronous
fs.stat(filePath, function(err, data) {
// If there was an error or it wasn't a directory...
if (err || !data.isDirectory()) {
// ...reject
reject(err || new Error("Not a directory");
} else {
// All good
resolve(data);
}
});
});
};
Of course, you may choose to have it resolve with false if the thing isn't a directory or any of several other choices; this just gets you further along.
For instance, having an error still be a rejection, but resolving with true/false for whether something that exists is a directory; this provides the maximum amount of information to the caller but makes them work a bit harder if all they care about is true/false:
var directoryExists = exports.directoryExists = function(filePath) {
return new Promise(function(resolve, reject) {
// Make the request asynchronous
fs.stat(filePath, function(err, data) {
if (err) {
// Reject on error
reject(err);
} else {
// Return result on success
resolve(data.isDirectory());
}
});
});
};
or making it always resolve, with false if there's no match or there is a match but it's not a directory:
var directoryExists = exports.directoryExists = function(filePath) {
return new Promise(function(resolve) {
// Make the request asynchronous
fs.stat(filePath, function(err, data) {
resolve(!err && data.isDirectory());
});
});
};
Lots of ways for this function to behave, it's up to you.

Promise won't resolve

I'm new to node and I'm having an issue with resolving an async Promise. My promise isn't resolving and I'm not sure what I did wrong. I'm still having troubles understanding promises and callbacks so any feedback is helpful.
var filterFiles = function(){
return new Promise(function(resolve, reject){
fs.readdir(rootDir, function(err, files){
if(err) return console.log(err);
var task = function(file){
return new Promise(function(resolve, reject){
if(! /^\..*/.test(file)){
fs.stat(rootDir + '/' + file, function(err, stats){
if(stats.isDirectory()){
dirArray.push(file);
console.log(dirArray.length);
resolve(file);
}
if(stats.isFile()){
fileArray.push(file);
console.log(fileArray.length);
resolve(file);
}
})
}
})
};
var actions = files.map(task);
return Promise.all(actions).then(function(resolve, reject){
resolve({dirArray: dirArray, fileArray: fileArray});
});
})
})
}
filterFiles().then(function(data){
console.log(data);
var obj = {
fileArray: fileArray,
dirArray: dirArray
};
res.send(obj);
})
It see at least three errors:
When you hit this if statement if(! /^\..*/.test(file)){ and it does not execute the if block, then the parent promise is never settled.
There is no error handling on fs.stat() so if you get an error on that call, you are ignoring that and will be attempting to use a bad value.
The error handling on your call to fs.readdir() is incomplete and will leave you with a promise that is never settled (when it should be rejected).
For a robust solution, you really don't want to be mixing promises and callbacks in the same code. It leads to the opportunity for lots of mistakes, particularly with error handling (as you can see you had at least three errors - two of which were in error handling).
If you're going to use Promises, then promisify the async operations you are using at the lowest level and use only promises to control your async code flow. The simplest way I know of to promisify the relevant fs operations is to use the Bluebird promise library with its Promise.promisifyAll(). You don't have to use that library. You could instead manually write promise wrappers for the async operations you're using.
Here's a version of your code using the Bluebird promise library:
const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));
function filterFiles() {
return fs.readdirAsync(rootDir).then(function(files) {
let fileArray = [];
let dirArray = [];
// filter out entries that start with .
files = files.filter(function(f) {
return !f.startsWith(".");
});
return Promise.map(files, function(f) {
return fs.statAsync(f).then(function(stats) {
if (stats.isDirectory()) {
dirArray.push(f);
} else {
fileArray.push(f);
}
});
}).then(function() {
// make the resolved value be an object with two properties containing the arrays
return {dirArray, fileArray};
});
});
}
filterFiles().then(function(data) {
res.json(data);
}).catch(function(err) {
// put whatever is appropriate here
res.status(500).end();
});
This was rewritten/restructured with these changes:
Use promises for all async operations
Fix all error handling to reject the returned promise
Filter out files starting with a . synchronously before processing any files (simplifies async processing).
Use Promise.map() to process an array of values in parallel.
In the filterFiles().then() handler, handle errors
You can't res.send() a Javascript object so I used res.json(data) instead (though I'm not sure what exactly you really want to send).
Replace regex comparison with more efficient and simpler to understand .startsWith().
If you don't want to use the Bluebird promise library, you can make your own promise wrappers for the fs methods you use like this:
fs.readdirAsync = function(dir) {
return new Promise(function(resolve, reject) {
fs.readdir(dir, function(err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
fs.statAsync = function(f) {
return new Promise(function(resolve, reject) {
fs.stat(f, function(err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
function filterFiles() {
return fs.readdirAsync(rootDir).then(function(files) {
let fileArray = [];
let dirArray = [];
// filter out entries that start with .
files = files.filter(function(f) {
return !f.startsWith(".");
});
return Promise.all(files.map(function(f) {
return fs.statAsync(f).then(function(stats) {
if (stats.isDirectory()) {
dirArray.push(f);
} else {
fileArray.push(f);
}
});
})).then(function() {
// make the resolved value be an object with two properties containing the arrays
return {dirArray, fileArray};
});
});
}
filterFiles().then(function(data) {
res.json(data);
}).catch(function(err) {
res.status(500).end();
});
The main issue you are having is that outer-most Promise is not resolved or rejected. You can fix this by resolving your Promise.all instead of returning it.
resolve(
Promise.all(actions)
.then(function(resolvedTasks){
// ... next potential issue is here
return {dirArray: dirArray, fileArray: fileArray}
})
);
(I know, kind of awkward-looking right?)
Next, your return value after the Promise.all resolves is a little weird. In the task function, you're pushing items onto dirArray and fileArray, but they are not declared or assigned in your snippet. I will assume that they are in-scope for this code. In this case, you just need to return your desired object.
Additionally, to make your async code more readable, here are some tips:
try not to mix callbacks with Promises
use a Promise library to promisify any code limited to callbacks. Example: bluebird's promisifyAll
avoid nesting callbacks/promises when possible

Node.js: How to run asynchronous code sequentially

I have this chunk of code
User.find({}, function(err, users) {
for (var i = 0; i < users.length; i++) {
pseudocode
Friend.find({
'user': curUser._id
}, function(err, friends) * * ANOTHER CALLBACK * * {
for (var i = 0; i < friends.length; i++) {
pseudocode
}
console.log("HERE I'm CHECKING " + curUser);
if (curUser.websiteaccount != "None") {
request.post({
url: 'blah',
formData: blah
}, function(err, httpResponse, body) { * * ANOTHER CALLBACK * *
pseudocode
sendMail(friendResults, curUser);
});
} else {
pseudocode
sendMail(friendResults, curUser);
}
});
console.log("finished friend");
console.log(friendResults);
sleep.sleep(15);
console.log("finished waiting");
console.log(friendResults);
}
});
There's a couple asynchronous things happening here. For each user, I want to find their relevant friends and concat them to a variable. I then want to check if that user has a website account, and if so, make a post request and grab some information there. Only thing is, that everything is happening out of order since the code isn't waiting for the callbacks to finish. I've been using a sleep but that doesn't solve the problem either since it's still jumbled.
I've looked into async, but these functions are intertwined and not really separate, so I wasn't sure how it'd work with async either.
Any suggestions to get this code to run sequentially?
Thanks!
I prefer the promise module to q https://www.npmjs.com/package/promise because of its simplicity
var Promises = require('promise');
var promise = new Promises(function (resolve, reject) {
// do some async stuff
if (success) {
resolve(data);
} else {
reject(reason);
}
});
promise.then(function (data) {
// function called when first promise returned
return new Promises(function (resolve, reject) {
// second async stuff
if (success) {
resolve(data);
} else {
reject(reason);
}
});
}, function (reason) {
// error handler
}).then(function (data) {
// second success handler
}, function (reason) {
// second error handler
}).then(function (data) {
// third success handler
}, function (reason) {
// third error handler
});
As you can see, you can continue like this forever. You can also return simple values instead of promises from the async handlers and then these will simply be passed to the then callback.
I rewrote your code so it was a bit easier to read. You have a few choices of what to do if you want to guarantee synchronous execution:
Use the async library. It provides some helper functions that run your code in series, particularly, this: https://github.com/caolan/async#seriestasks-callback
Use promises to avoid making callbacks, and simplify your code APIs. Promises are a new feature in Javascript, although, in my opinion, you might not want to do this right now. There is still poor library support for promises, and it's not possible to use them with a lot of popular libraries :(
Now -- in regards to your program -- there's actually nothing wrong with your code at all right now (assuming you don't have async code in the pseucode blocks). Your code right now will work just fine, and will execute as expected.
I'd recommend using async for your sequential needs at the moment, as it works both server and client side, is essentially guaranteed to work with all popular libraries, and is well used / tested.
Cleaned up code below
User.find({}, function(err, users) {
for (var i = 0; i < users.length; i++) {
Friend.find({'user':curUser._id}, function(err, friends) {
for (var i = 0; i < friends.length; i++) {
// pseudocode
}
console.log("HERE I'm CHECKING " + curUser);
if (curUser.websiteaccount != "None") {
request.post({ url: 'blah', formData: 'blah' }, function(err, httpResponse, body) {
// pseudocode
sendMail(friendResults, curUser);
});
} else {
// pseudocode
sendMail(friendResults, curUser);
}
});
console.log("finished friend");
console.log(friendResults);
sleep.sleep(15);
console.log("finished waiting");
console.log(friendResults);
}
});
First lets go a bit more functional
var users = User.find({});
users.forEach(function (user) {
var friends = Friend.find({
user: user._id
});
friends.forEach(function (friend) {
if (user.websiteaccount !== 'None') {
post(friend, user);
}
sendMail(friend, user);
});
});
Then lets async that
async.waterfall([
async.apply(Users.find, {}),
function (users, cb) {
async.each(users, function (user, cb) {
async.waterfall([
async.apply(Friends.find, { user, user.id}),
function (friends, cb) {
if (user.websiteAccount !== 'None') {
post(friend, user, function (err, data) {
if (err) {
cb(err);
} else {
sendMail(friend, user, cb);
}
});
} else {
sendMail(friend, user, cb);
}
}
], cb);
});
}
], function (err) {
if (err) {
// all the errors in one spot
throw err;
}
console.log('all done');
});
Also, this is you doing a join, SQL is really good at those.
You'll want to look into something called promises. They'll allow you to chain events and run them in order. Here's a nice tutorial on what they are and how to use them http://strongloop.com/strongblog/promises-in-node-js-with-q-an-alternative-to-callbacks/
You can also take a look at the Async JavaScript library: Async It provides utility functions for ordering the execution of asynchronous functions in JavaScript.
Note: I think the number of queries you are doing within a handler is a code smell. This problem is probably better solved at the query level. That said, let's proceed!
It's hard to know exactly what you want, because your psuedocode could use a cleanup IMHO, but I'm going to what you want to do is this:
Get all users, and for each user
a. get all the user's friends and for each friend:
send a post request if the user has a website account
send an email
Do something after the process has finished
You can do this many different ways. Vanilla callbacks or async work great; I'm going to advocate for promises because they are the future, and library support is quite good. I'll use rsvp, because it is light, but any Promise/A+ compliant library will do the trick.
// helpers to simulate async calls
var User = {}, Friend = {}, request = {};
var asyncTask = User.find = Friend.find = request.post = function (cb) {
setTimeout(function () {
var result = [1, 2, 3];
cb(null, result);
}, 10);
};
User.find(function (err, usersResults) {
// we reduce over the results, creating a "chain" of promises
// that we can .then off of
var userTask = usersResults.reduce(function (outerChain, outerResult) {
return outerChain.then(function (outerValue) {
// since we do not care about the return value or order
// of the asynchronous calls here, we just nest them
// and resolve our promise when they are done
return new RSVP.Promise(function (resolveFriend, reject){
Friend.find(function (err, friendResults) {
friendResults.forEach(function (result) {
request.post(function(err, finalResult) {
resolveFriend(outerValue + '\n finished user' + outerResult);
}, true);
});
});
});
});
}, RSVP.Promise.resolve(''));
// handle success
userTask.then(function (res) {
document.body.textContent = res;
});
// handle errors
userTask.catch(function (err) {
console.log(error);
});
});
jsbin

Using Async waterfall in node.js

I have 2 functions that I'm running asynchronously. I'd like to write them using waterfall model. The thing is, I don't know how..
Here is my code :
var fs = require('fs');
function updateJson(ticker, value) {
//var stocksJson = JSON.parse(fs.readFileSync("stocktest.json"));
fs.readFile('stocktest.json', function(error, file) {
var stocksJson = JSON.parse(file);
if (stocksJson[ticker]!=null) {
console.log(ticker+" price : " + stocksJson[ticker].price);
console.log("changing the value...")
stocksJson[ticker].price = value;
console.log("Price after the change has been made -- " + stocksJson[ticker].price);
console.log("printing the the Json.stringify")
console.log(JSON.stringify(stocksJson, null, 4));
fs.writeFile('stocktest.json', JSON.stringify(stocksJson, null, 4), function(err) {
if(!err) {
console.log("File successfully written");
}
if (err) {
console.error(err);
}
}); //end of writeFile
} else {
console.log(ticker + " doesn't exist on the json");
}
});
} // end of updateJson
Any idea how can I write it using waterfall, so i'll be able to control this? Please write me some examples because I'm new to node.js
First identify the steps and write them as asynchronous functions (taking a callback argument)
read the file
function readFile(readFileCallback) {
fs.readFile('stocktest.json', function (error, file) {
if (error) {
readFileCallback(error);
} else {
readFileCallback(null, file);
}
});
}
process the file (I removed most of the console.log in the examples)
function processFile(file, processFileCallback) {
var stocksJson = JSON.parse(file);
if (stocksJson[ticker] != null) {
stocksJson[ticker].price = value;
fs.writeFile('stocktest.json', JSON.stringify(stocksJson, null, 4), function (error) {
if (err) {
processFileCallback(error);
} else {
console.log("File successfully written");
processFileCallback(null);
}
});
}
else {
console.log(ticker + " doesn't exist on the json");
processFileCallback(null); //callback should always be called once (and only one time)
}
}
Note that I did no specific error handling here, I'll take benefit of async.waterfall to centralize error handling at the same place.
Also be careful that if you have (if/else/switch/...) branches in an asynchronous function, it always call the callback one (and only one) time.
Plug everything with async.waterfall
async.waterfall([
readFile,
processFile
], function (error) {
if (error) {
//handle readFile error or processFile error here
}
});
Clean example
The previous code was excessively verbose to make the explanations clearer. Here is a full cleaned example:
async.waterfall([
function readFile(readFileCallback) {
fs.readFile('stocktest.json', readFileCallback);
},
function processFile(file, processFileCallback) {
var stocksJson = JSON.parse(file);
if (stocksJson[ticker] != null) {
stocksJson[ticker].price = value;
fs.writeFile('stocktest.json', JSON.stringify(stocksJson, null, 4), function (error) {
if (!err) {
console.log("File successfully written");
}
processFileCallback(err);
});
}
else {
console.log(ticker + " doesn't exist on the json");
processFileCallback(null);
}
}
], function (error) {
if (error) {
//handle readFile error or processFile error here
}
});
I left the function names because it helps readability and helps debugging with tools like chrome debugger.
If you use underscore (on npm), you can also replace the first function with _.partial(fs.readFile, 'stocktest.json')
First and foremost, make sure you read the documentation regarding async.waterfall.
Now, there are couple key parts about the waterfall control flow:
The control flow is specified by an array of functions for invocation as the first argument, and a "complete" callback when the flow is finished as the second argument.
The array of functions are invoked in series (as opposed to parallel).
If an error (usually named err) is encountered at any operation in the flow array, it will short-circuit and immediately invoke the "complete"/"finish"/"done" callback.
Arguments from the previously executed function are applied to the next function in the control flow, in order, and an "intermediate" callback is supplied as the last argument. Note: The first function only has this "intermediate" callback, and the "complete" callback will have the arguments of the last invoked function in the control flow (with consideration to any errors) but with an err argument prepended instead of an "intermediate" callback that is appended.
The callbacks for each individual operation (I call this cbAsync in my examples) should be invoked when you're ready to move on: The first parameter will be an error, if any, and the second (third, fourth... etc.) parameter will be any data you want to pass to the subsequent operation.
The first goal is to get your code working almost verbatim alongside the introduction of async.waterfall. I decided to remove all your console.log statements and simplified your error handling. Here is the first iteration (untested code):
var fs = require('fs'),
async = require('async');
function updateJson(ticker,value) {
async.waterfall([ // the series operation list of `async.waterfall`
// waterfall operation 1, invoke cbAsync when done
function getTicker(cbAsync) {
fs.readFile('stocktest.json',function(err,file) {
if ( err ) {
// if there was an error, let async know and bail
cbAsync(err);
return; // bail
}
var stocksJson = JSON.parse(file);
if ( stocksJson[ticker] === null ) {
// if we don't have the ticker, let "complete" know and bail
cbAsync(new Error('Missing ticker property in JSON.'));
return; // bail
}
stocksJson[ticker] = value;
// err = null (no error), jsonString = JSON.stringify(...)
cbAsync(null,JSON.stringify(stocksJson,null,4));
});
},
function writeTicker(jsonString,cbAsync) {
fs.writeFile('stocktest.json',jsonString,function(err) {
cbAsync(err); // err will be null if the operation was successful
});
}
],function asyncComplete(err) { // the "complete" callback of `async.waterfall`
if ( err ) { // there was an error with either `getTicker` or `writeTicker`
console.warn('Error updating stock ticker JSON.',err);
} else {
console.info('Successfully completed operation.');
}
});
}
The second iteration divides up the operation flow a bit more. It puts it into smaller single-operation oriented chunks of code. I'm not going to comment it, it speaks for itself (again, untested):
var fs = require('fs'),
async = require('async');
function updateJson(ticker,value,callback) { // introduced a main callback
var stockTestFile = 'stocktest.json';
async.waterfall([
function getTicker(cbAsync) {
fs.readFile(stockTestFile,function(err,file) {
cbAsync(err,file);
});
},
function parseAndPrepareStockTicker(file,cbAsync) {
var stocksJson = JSON.parse(file);
if ( stocksJson[ticker] === null ) {
cbAsync(new Error('Missing ticker property in JSON.'));
return;
}
stocksJson[ticker] = value;
cbAsync(null,JSON.stringify(stocksJson,null,4));
},
function writeTicker(jsonString,cbAsync) {
fs.writeFile('stocktest.json',jsonString,,function(err) {
cbAsync(err);
});
}
],function asyncComplete(err) {
if ( err ) {
console.warn('Error updating stock ticker JSON.',err);
}
callback(err);
});
}
The last iteration short-hands a lot of this with the use of some bind tricks to decrease the call stack and increase readability (IMO), also untested:
var fs = require('fs'),
async = require('async');
function updateJson(ticker,value,callback) {
var stockTestFile = 'stocktest.json';
async.waterfall([
fs.readFile.bind(fs,stockTestFile),
function parseStockTicker(file,cbAsync) {
var stocksJson = JSON.parse(file);
if ( stocksJson[ticker] === null ) {
cbAsync(new Error('Missing ticker property in JSON.'));
return;
}
cbAsync(null,stocksJson);
},
function prepareStockTicker(stocksJson,cbAsync) {
stocksJson[ticker] = value;
cbAsync(null,JSON.stringify(stocksJson,null,4));
},
fs.writeFile.bind(fs,stockTestFile)
],function asyncComplete(err) {
if ( err ) {
console.warn('Error updating stock ticker JSON.',err);
}
callback(err);
});
}
Basically nodejs (and more generally javascript) functions that require some time to execute (be it for I/O or cpu processing) are typically asynchronous, so the event loop (to make it simple is a loop that continuously checks for tasks to be executed) can invoke the function right below the first one, without getting blocked for a response. If you are familiar with other languages like C or Java, you can think an asynchronous function as a function that runs on another thread (it's not necessarily true in javascript, but the programmer shouldn't care about it) and when the execution terminates this thread notifies the main one (the event loop one) that the job is done and it has the results.
As said once the first function has ended its job it must be able to notify that its job is finished and it does so invoking the callback function you pass to it. to make an example:
var callback = function(data,err)
{
if(!err)
{
do something with the received data
}
else
something went wrong
}
asyncFunction1(someparams, callback);
asyncFunction2(someotherparams);
the execution flow would call: asyncFunction1, asyncFunction2 and every function below until asyncFunction1 ends, then the callback function which is passed as the last parameter to asyncFunction1 is called to do something with data if no errors occurred.
So, to make 2 or more asynchronous functions execute one after another only when they ended you have to call them inside their callback functions:
function asyncTask1(data, function(result1, err)
{
if(!err)
asyncTask2(data, function(result2, err2)
{
if(!err2)
//call maybe a third async function
else
console.log(err2);
});
else
console.log(err);
});
result1 is the return value from asyncTask1 and result2 is the return value for asyncTask2. You can this way nest how many asynchronous functions you want.
In your case if you want another function to be called after updateJson() you must call it after this line:
console.log("File successfully written");

Categories

Resources