samedi 7 mars 2015

Managing asynchronous Callbacks in Meteor

I'm using Meteor 1.* and Iron Router 1.*.


I'm using Node.js calls on the server side in Meteor, outside of a Meteor-method -- specifically inside a server side Iron Router route.


A portion of the code inside of the route looks similar to the following so far:



fs.mkdir(filebox, function (e) {
if(!e || e.code === 'EEXIST') {
fs.writeFile(file1, function (err) {
if (err) throw err;

fs.writeFile(file2, function (err) {
if (err) throw err;

fs.writeFile(file.3, function (err) {
if (err) throw err;

ExternalLibrary.do_something_(file1, function (err, buffer) {
if (err) throw err;

ExternalLibrary.do_something_(file2, function (err, buffer) {
if (err) throw err;

ExternalLibrary.do_something_(file3, function (err, buffer) {
if (err) throw err;

some_object = { first: file1, second: file2 }

ExternalLibrary.do_something_else_(some_object, function (err, buffer) {
if (err) throw err;

fs.readFile(file1, function (err, data) {
if (err) throw err;

res.write(data);
res.end();
});
});
});
});
});
});
});
});
} else {
console.log(e);
}
});


My problem is, I need to add even more calls to fs.write and the ExternalLibrary and further make these calls conditional.


It looks like I'm entering Callback hell.


On Callbacks


I know that Meteor uses Coroutines (or fibers, or continuations), but I don't know anything about how this works. And that within a Meteor-method we have the option of using Meteor.wrapAsync.


I've read some on Node.js Promises and Generators. And specifically I'm trying out the frozeman/q-meteor library.


Question


What is the best way to 'flatten' this tree and save myself from Callback hell? I want a solution that will allow for conditional method calls too. For example, I'm going to eventually need to add something like the following to the code example above:



if (some_condition === true) {
// call this method or node function
fs.writeFile(file4, function (err) {
fs.writeFile(file5, function (err) {
// do something
}
}
}
else {
// call this method or node function
fs.writeFile(file6, function (err) {
fs.writeFile(file7, function (err) {
// do something
}
}
}


EDIT


I think I've almost got things working with the Q library. One issue I can't get past is that some files can be written in parallel and sometimes I need the files to completely finish writing, as the next function needs access to them.


Here's what I have so far:



makeDirectory(filebox)
.then(writeFile(file1, data))
.then(writeFile(file2, data))
.then(writeFile(file3, data))
.then(ExternalLibrary._DoSomethingWithWrittenFiles(file1))
.then(ExternalLibrary._DoSomethingWithWrittenFiles(file2))
.then(ExternalLibrary._DoSomethingWithWrittenFiles(file3))
.then(ExternalLibrary._PerformAnotherOperationOnFiles(file1, file2))
.then(readFile(filel1))
.then(function (result) { //output from readFile
console.log('after read file', result);
res.write(result);
res.end();
})
.catch(function (error) {
console.log('catch error', error);
})
.done(function (result) {
console.log('done', result);
});


The writeFile methods can all run in parallel, but the ExternalLibrary functions need these files to finish writing completely before it can perform it's work -- right now when node gets to the ExternalLibrary calls I get an error stating that the files don't exist.


This is what my deferred functions look like:



writeFile = function (dst, data) {
console.log('desination:', dst);
// console.log('data:', data);
var fs = Npm.require('fs');
var deferred = Q.defer();
fs.writeFile(dst, data, function (err, result) {
if (err) {
deferred.reject(new Error(err));
}
else {
deferred.resolve();
}
});
return deferred.promise;
}

readFile = function (src) {
var fs = Npm.require('fs');
var deferred = Q.defer();
fs.readFile(src, function (err, result) {
if (err) {
deferred.reject(new Error(err));
}
else {
// console.log('readFile method, with result:', result);
deferred.resolve(result);
}
});
return deferred.promise;
}

makeDirectory = function (dst) {
var fs = Npm.require('fs');
var deferred = Q.defer();
fs.mkdir(dst, function (err, result) {
if(!err || err.code === 'EEXIST') {
deferred.resolve();
}
else {
deferred.reject(err);
}
});
return deferred.promise;
}


And this is a sample of what the ExternalLibrary looks like:



ExternalLibrary._DoSomethingWithWrittenFiles = function(file) {
var deferred = Q.defer();
ExternalLibrary.executeCommand([file, 'do_something', 'output ', output], function (error) {
if (error) {
console.log(error);
deferred.reject(new Error(error));
}
else {
deferred.resolve();
}
});
return deferred.promise;
};

ExternalLibrary.executeCommand = function (args, callback) {
var command = 'file_command ' + args.join(' ');
console.log(command);
exec(command, {encoding: 'binary', maxBuffer: 1024 * 1000}, function(err, stdout, stderr) {
if(err) return callback(new Error(err));
callback(null);
});
};

Aucun commentaire:

Enregistrer un commentaire