Reputation: 399
I'm not quite sure, and maybe I'm missing something obvious, but I cannot figure out how to chain two promises.
My callback-based code looks something like this:
async.series([
function (cb) {
// Create the directory if the nodir switch isn't on
if (!nodir) {
fs.mkdir('somedirectory', function (err) {
if (err) {
log('error while trying to create the directory:\n\t %s', err)
process.exit(0)
}
log('successfully created directory at %s/somedirectory', process.cwd())
cb(null)
})
}
cb(null)
},
function (cb) {
// Get the contents of the sample YML
fs.readFile(path.dirname(__dirname) + '/util/sample_config.yml', function (err, c) {
var sampleContent = c
if (err) {
log('error while reading the sample file:\n\t %s', err)
process.exit(0)
}
log('pulled sample content...')
// Write the config file
fs.writeFile('db/config.yml', sampleContent, function (err) {
if (err) {
log('error writing config file:\n\t %s', err)
process.exit(0)
}
log('successfully wrote file to %s/db/config.yml', process.cwd())
cb(null)
})
})
}
])
I've begun to try and refactor this to promise-based flow, this is what I have so far:
if (!nodir) {
fs.mkdirAsync('somedirectory').then(function () {
log('successfully created directory at %s/somedirectory', process.cwd())
}).then(function () {
// how can I put fs.readFileAsync here? where does it fit?
}).catch(function (err) {
log('error while trying to create the directory:\n\t %s', err)
process.exit(0)
})
}
(I'm using Bluebird, so I did Promise.promisifyAll(fs)
before)
The problem is, I have no idea where to put the second "step" of the previous series. Do I put it in then
or in its function? Do I return it and put it in a separate function?
Any help would greatly be appreciated.
Upvotes: 5
Views: 5415
Reputation: 276596
Since you're using Bluebird, you can use a lot of sugar and have much much cleaner code than the accepted answer IMO:
var fs = Promise.promisifyAll(fs); // tell bluebird to work with FS
Promise.try(function(){
if(nodir) return fs.mkdirAsync('somedirectory').
catch(catchErr("Could not create dir"));
}).then(function(){
return fs.readFileAsync(path.dirname(__dirname) + '/util/sample_config.yml').
catch(catchErr("error while reading the sample file"));
}).then(function(data){
log('pulled sample content...');
return fs.writeFile('db/config.yml', data).
catch(catchErr("error writing config file"));
}).then(function(){
log('successfully wrote file to %s/db/config.yml', process.cwd())
}, function(err){
// centralized error handling, to remove the redundancy
log(err.message);
log(err.internal);
log(err.stack); // this is important!
});
function catchErr(msg){ // helper to rethrow with a specific message
return function(e){
var err = new Error(msg);
err.internal = e; // wrap the error;
throw e;
};
}
I'd go further though, and would remove the finer grained errors you have here since the types you use really provide no additional info over the built in messages these API methods provide - shortening your code to:
Promise.try(function(){
if(nodir) return fs.mkdirAsync("somedirectory");
}).then(function(){
fs.readFileAync(path.dirname(__dirname) + '/util/sample_config.yml');
}).then(function(data){
log('pulled sample content...');
return fs.writeFile('db/config.yml', data);
}).then(function(){
log('successfully wrote file to %s/db/config.yml', process.cwd())
}).catch(function(err){
log(err);
process.exit(1);
});
Promises are throw safe and provide sane and certralized error handling - pretty nice win if you ask me. It gets better though, if you're on io.js or modern node you can use:
Promise.coroutine(function*(){ // generators ftw
if(nodir) yield fs.mkdirAsync("somedirectory");
var data = yield fs.readFileAsync(path.dirname(__dirname) + '/util/sample_config.yml');
log("pulled sample content");
yield fs.writeFileAsync("db/config.yml", data);
log('successfully wrote file to %s/db/config.yml', process.cwd());
})();
process.on("unhandledRejection", function(p, r){
throw r; // quit process on promise failing
});
Upvotes: 2
Reputation: 211740
Normally promise-driven code looks like this:
operation.then(function(result) {
return nextOperation();
}).then(function(nextResult) {
return finalOperation();
}).then(function(finalResult) {
})
There's a lot going on in your example, but the general idea would be something like:
Promise.resolve().then(function() {
if (nodir) {
return fs.mkdir('somedirectory').catch(function(err) {
log('error while trying to create the directory:\n\t %s', err);
process.exit(0);
});
}
}).then(function() {
return fs.readFile(path.dirname(__dirname) + '/util/sample_config.yml').catch(function(err) {
log('error while reading the sample file:\n\t %s', err);
process.exit(0);
})
}).then(function(sampleContent) {
log('pulled sample content...');
return fs.writeFile('db/config.yml', sampleContent).catch(function(err) {
log('error writing config file:\n\t %s', err)
process.exit(0)
})
}).then(function() {
log('successfully wrote file to %s/db/config.yml', process.cwd())
})
This presumes all the calls you're using are promise native.
The original Promise.resolve()
is simply something to start out the chain with a promise since your first step is conditional.
Upvotes: 5