Async Patterns
Read some JSON
function readJSONSync(filename) {
return JSON.parse(fs.readFileSync(filename, 'utf8'));
};
// error handling
function readJSONSync(filename) {
var file = fs.readFileSync(filename, 'utf8');
try {
file = JSON.parse(file);
} catch (e) {
file = e;
}
return file;
};
// naive async
function readJSON(filename, callback) {
fs.readFile(filename, 'utf8', function(err, file) {
if (err) {
return callback(err);
}
return callback(null, JSON.parse(file));
});
};
// naive error handling
function readJSON(filename, callback) {
fs.readFile(filename, 'utf8', function(err, file) {
if (err) {
return callback(err);
}
try {
callback(null, JSON.parse(file));
} catch(e) {
callback(e);
}
};
};
// dont catch errors thrown by callback itself
function readJSON(filename, callback) {
fs.readFile(filename, 'utf8', function(err, file) {
var res;
if (err) {
return callback(err);
}
try {
res = JSON.parse(file);
} catch(e) {
return callback(e);
}
callback(null, res);
};
};
Promises
`readFile` helper
funciton readFile(filename, enc) {
return new Promise(function(fullfill, reject) {
fs.readFile(filename, enc, function(err, file) {
if (err) {
return reject(err);
}
return fullfill(file);
});
});
};
Simple Promise
function readJSON(filename) {
return readFile(filename).then(function(file) {
return JSON.parse(file);
});
};
function(filename) {
return readFile(filename).then(JSON.parse);
};
function readJSON(filename) {
return readFile(filename).then(JSON.parse);
};
function(filename) {
return JSON.parse(fs.readFileSync(filename, 'utf8'));
};
More Complex
function doSomething(getX, getY, callback) {
var x, y;
getX(function(err, xval) {
if (err) {
x = err;
}
x = xval;
if (isError(y)) {
callback(y);
} else {
callback(null, x*y);
}
});
getX(function(err, yval) {
if (err) {
y = err;
}
y = yval;
if (isError(x)) {
callback(x);
} else {
callback(null, x*y);
}
});
}
doSomething(fetchX, fetchY, function(err, res) {
console.log(res);
});
async.parallel
function doSomething(getX, getY, callback) {
async.parallel([getX, getY], function(err, res) {
if (err) {
return callback(err);
}
callback(null, res[0]*res[1]);
});
}
function doSomething(promiseX, promiseY) {
return Promise.all([promiseX, promiseY])
.then(function(res) {
return res[0]*res[1];
});
}
Another example
function transformFile(in, out, callback) {
fs.readFile(in, 'utf8', function(err, file) {
if (err) {
return callback(err);
}
fs.writeFile(out, file.replace(/red/g, 'blue'), function(err) {
if (err) {
return callback(err);
}
callback(null);
}
})
}
async.waterfall
function transformFile(in, out, callback) {
async.waterfall([
fs.readFile.bind(fs, in),
function(file, cb) {
cb(null, file.replace(/red/g, 'blue'));
},
fs.writeFile.bind(fs, out)
], callback)
}
function transformFile(in, out, callback) {
return fs.readFileAsync(in, 'utf8')
.then(function(file) {
return file.replace(/red/g, 'blue');
}).then(fs.writeFileAsync);
}
`fs.readFileAsync` and `fs.writeFileAsync` return promises.
Generators
function* count(){
var x;
for (x = 0; true; x++) {
yield x;
}
}
for (var c of count()) {
console.log(c);
}
var c = count();
c.next().value // 0
c.next().value // 1
function* fibonacci() {
var vals = [0, 1];
while(true) {
vals = [vals[1], vals[0] + vals[1]]
yield vals[1];
}
}
for (var f of fibonacci()) {
console.log(f);
}
var fib = fib();
fib.next().value // 1
fib.next().value // 2
fib.next().value // 3
fib.next().value // 5
fib.next().value // 8
Spawn
function spawn(makeGenerator) {
(function () {
// when verb is "send", arg is a value
// when verb is "throw", arg is an exception
function continuer(verb, arg) {
var result;
try {
result = generator[verb](arg);
} catch (exception) {
return reject(exception);
}
if (result.done) {
return result.value;
} else {
return Q(result.value).then(callback, errback);
}
}
var generator = makeGenerator.apply(this, arguments);
var callback = continuer.bind(continuer, "next");
var errback = continuer.bind(continuer, "throw");
return callback();
})().done();
}
// taken from Q.async/Q.spawn
spawn(function* () {
var user = yield getUser();
updateUser(user);
}
FRP
function movieSearch(query) {
if (query.length < 3)
return Bacon.once([]); // show no results for queries of length < 3
return Bacon.fromPromise(queryMovie(query));
}
var text = $('#input')
.asEventStream('keydown') // stream of keydown events from text-field
.debounce(300) // limit the rate of queries
.map(function(event) { // get input text value from each event
return event.target.value;
})
.skipDuplicates(); // Ignore duplicate events with same text
// React to text changes by doing a lookup with api function movieSearch,
// creating a new observable with the results.
//
// Make sure to always react to the latest, value even
// if responses from the server arrives out of order
var suggestions =
text.flatMapLatest(movieSearch);
// Display "Searching..." when waiting for the results
text.awaiting(suggestions).onValue(function(x) {
if (x) $('#results').html('Searching...');
});
// Render suggestion results to DOM
suggestions.onValue(function(results) {
$('#results').html($.map(results, showMovie));
});
Honorable Mentions
highland.js
http://highlandjs.org/
Resources
- https://www.promisejs.org/
- https://promisesaplus.com/
- html5rocks promises
- MDN Promises
- MDN Generators
- getify's You Dont Know JS: Promises
- getify's You Dont Know JS: Generators
- Promise Nuggets
- Why I am Switching to Promises
- Analysis of generators and other async patterns in node
- Domenic's Callbacks, Promises, and Coroutines (oh my)
- BaconJS
Async Patterns
By Gary Katsevman
Async Patterns
- 451