Bryant Rolfe
Call my function when you're done doing your async thing, please.
var myAsyncFunction = function(foo, bar, baz, callback){
setTimeout(function(){
var result = foo + bar + baz;
callback(null, result);
}, 500);
};
// callback signature: callback(err, results)
fs.readFile('~/.profile-user',
// some time passes...
function(err, contents){
// now we have the contents!
}
);
Let's see a more realistic example...
var async = require('async'),
fs = require('fs');
var paths = ['file1.txt', 'file2.txt', 'file3.txt'];
async.map(paths, fs.stat, function(error, results) {
// use the stat results
});
Not bad, right?
Until we want to re-use a piece of that information...
var paths = ['file1.txt', 'file2.txt', 'file3.txt'];
async.map(paths, fs.stat, function(error, results) {
// use the results
});
fs.stat(paths[0], function(error, stat) {
// use stat.size
});
But now we are stat-ing the file twice!
Fine, we'll just handle the first one specially...
var paths = ['file1.txt', 'file2.txt', 'file3.txt'];
async.map(paths, fs.stat, function(error, results) {
var size = results[0].size;
// use size
// use the results
})
Now our size task is blocking on all those other files!
Ok. Then we'll just handle that file on its own...
var paths = ['file1.txt', 'file2.txt', 'file3.txt'],
file1 = paths.shift();
fs.stat(file1, function(error, stat) {
// use stat.size
async.map(paths, fs.stat, function(error, results) {
results.unshift(stat);
// use the results
});
});
Now all the other files block on the first one. Sad face.
Last try!
var paths = ['file1.txt', 'file2.txt', 'file3.txt'],
file1 = paths.shift();
async.parallel([
function(callback) {
fs.stat(file1, function(error, stat) {
// use stat.size
callback(error, stat);
});
},
function(callback) {
async.map(paths, fs.stat, callback);
}
], function(error, results) {
var stats = [results[0]].concat(results[1]);
// use the stats
});
Victory!
That wasn't terribly elegant...
We can do better.
A promise is...
These methods return new promises
Note: There are many promise libraries out there. Some are better than others. See this blog post for more info.
A toy example...
var promise = fsStat('~/.profile-user');
promise.then(function(contents){
// We have the contents!
});
Got it?
Let's revist our original example...
var fs = require('fs'),
Q = require('q');
var fsStat = Q.nfbind(fs.stat); // Create a promise version of fs.stat
var paths = ['file1.txt', 'file2.txt', 'file3.txt'];
var statsPromises = paths.map(fsStat);
// We now have promise objects for all the stat operations
// When all of the promises are resolved, use their results
Q.all(statsPromises).then(function(results){
// use the results
});
What about the size part?
statsPromises[0].then(function(stat){
// use the size
});
All together now!
var paths = ['file1.txt', 'file2.txt', 'file3.txt'];
var statsPromises = paths.map(fsStat);
// We now have promise objects for all the stat operations
// When all of the promises are resolved, use their results
Q.all(statsPromises).then(function(results){
// use the results
});
statsPromises[0].then(function(stat){
// use the size
});
Ahhh...that's better
What did we just do?
Where are we?
Remember that promise.then()
returns a new promise:
var anotherPromise = promise.then(function(){});
When anotherPromise
resolves depends on what the handler for then
returns.
Can return simple data...
fsStat('file1.txt')
.then(function(stat){
// called when fsStat completes
var transformedData;
// transform the data...
return transformedData;
})
.then(function(transformedData){
// called after previous handler returns with its results
console.log(transformedData);
});
The chained promise resolves immediately after handler returns.
Can return promises...
fsExists('file1.txt')
.then(function(exists) {
if(exists) {
return fsStat('file1.txt');
} else {
return fsWriteFile('file1.txt', "some data").then(function() {
return fsStat('file1.txt')
});
}
})
.then(function(stat) {
// called only after all chained promises prior to this one resolve
console.log(stat);
});
Chained promise resolves after promises returned by previous handlers resolve.
Can aggregate promises...
var p1 = fsStat('file1.txt');
var p2 = fsStat('file2.txt');
Q.all(p1, p2).then(function(results){
var file1Stats = results.shift();
var file2Stats = results.shift();
// do something with both stats
})
Really useful pattern for rendering a view that might depend on two separate ajax fetches.
Can execute async tasks sequentially...
var paths = ['file1.txt', 'file2.txt', 'file3.txt'],
var allDone = paths.reduce(function(promise, currentPath){
return soFar.then(function(){
return fsUnlink(currentPath);
});
}, Q());
allDone.then(function() {
// all files have been unlinked
})
// Q() returns a pre-resolved promise to start the chain
We can all agree...
that promises are sweet.
There's a whole lot more to learn.