Skip to content

Instantly share code, notes, and snippets.

@DmitrySoshnikov
Created May 29, 2013 06:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DmitrySoshnikov/5668283 to your computer and use it in GitHub Desktop.
Save DmitrySoshnikov/5668283 to your computer and use it in GitHub Desktop.
"use strict";
const slice = [].slice;
/**
* A user callback function that is raised when the result of an async function
* is available.
*
* @param Any error
* If truthy, indicates an error result. Will be thrown from the
* generator.
* @param Any result
* If successful this result will be the value of the `yield`.
*/
function Callback(error, result){}
/**
* A function that can be takes a callback as its last parameter.
*
* @params Any args
* Any amount of arguments.
* @return Continuation
* The continuation for the args as applied to this Continuable.
*/
function Continuable(/*...args*/){}
/**
* The type of function that must be yielded from a generator being run.
*
* @param Callback
* The callback which will handle the error or result of the
* continuation.
*/
function Continuation(callback){}
/**
* Uses continuation passing style to pump through each yield in a generator.
*
* @param Generator generator
* The generator object to pump.
* @param Continuation cont
* The continuation which will be passed the callback that dispatches to
* the generator.
*/
function pump(generator, cont){
cont(function(err, result){
if (err) {
generator.throw(err);
} else {
const next = generator.send(result).value;
next && pump(generator, next);
}
});
}
/**
* Creates a generator from a generator function and begins executing it.
*
* @param GeneratorFunction generatorFn
* The generator function to execute. Every yielded value should be a
* function that works with `pump`, such as those created with `wrap`.
*/
exports.run = function run(generatorFn){
const generator = generatorFn();
pump(generator, generator.next().value);
};
/**
* Wrap an async function as a Continuable function so it can be used with
* `run`.
*
* @param Function fn
* Function to wrap. This function must accept a Callback as its last
* parameter.
* @return Continuable
* Wrapped version of the function that can be yielded to in a
* generator executed using `run`.
*/
exports.makeContinuable = function makeContinuable(fn){
return function(){
const args = slice.call(arguments);
const receiver = this;
return function(cb){
fn.apply(receiver, args.concat(cb));
};
};
};
/**
* Helper that can be used to pause execution.
*
* @param Number ms
* Time to pause.
* @return Continuation
* Function that can be be yielded to in a generator executed using
* `run`.
*/
exports.sleep = function sleep(ms){
return function(cb){
const start = Date.now();
setTimeout(function(){
cb(null, Date.now() - start);
}, ms);
};
};
/**
* Helper that waits until the next event loop tick.
*
* @return Continuation
* Function that can be be yielded to in a generator executed using
* `run`.
*/
exports.tick = function tick(){
return function(cb){
process.nextTick(cb);
};
};
/**
* Defers execution until the end of the current microtask
*
* @return Continuation
* Function that can be be yielded to in a generator executed using
* `run`.
*/
exports.defer = function defer(){
return function(fn){
setImmediate(fn);
};
};
/**
* Maps a Continuable function over a set of values. Meant to be used with
* delegating yield (yield*).
*
* @param Array array
* The set of values to map over.
* @param Continuable fn
* The function to be applied to each value.
* @return Array
* The mapped array.
*/
exports.map = function* map(array, cb, receiver){
const result = [];
for (let i = 0; i < array.length; i++) {
if (i in array) {
result.push(yield cb.call(receiver, array[i], i, array));
}
}
return result;
};
/**
* Uses a Continuable function to filter a set of values. Meant to be used with
* delegating yield (yield*).
*
* @param Array array
* The set of values to map over.
* @param Continuable fn
* The function to be applied to each value.
* @return Array
* The filtered array.
*/
exports.filter = function* filter(array, cb, receiver){
const result = [];
for (let i = 0; i < array.length; i++) {
if (i in array && (yield cb.call(receiver, array[i], i, array))) {
result.push(array[i]);
}
}
return result;
};
/**
* Uses a Continuable function to reduce a set of values. Meant to be used with
* delegating yield (yield*).
*
* @param Array array
* The set of values to map over.
* @param Continuable fn
* The function to be applied to each value.
* @return Any
*/
exports.reduce = function* reduce(array, cb, initial){
let index, accum;
if (arguments.length < 3) {
index = 1;
accum = array[0];
} else {
index = 0;
accum = initial;
}
for (let i = index; i < array.length; i++) {
if (i in array) {
accum = yield cb(accum, array[i], array);
}
}
return accum;
};
/**
* Returns true the first time a Continuable function returns a truthy value
* against a set of values, otherwise returns false. Meant to be used with
* delegating yield (yield*).
*
* @param Array array
* The set of values to map over.
* @param Continuable fn
* The function to be applied to each value.
* @return Boolean
*/
exports.some = function* some(array, cb, receiver){
for (let i = 0; i < array.length; i++) {
if (i in array && (yield cb.call(receiver, array[i], i, array))) {
return true;
}
}
return false;
};
/**
* Returns false the first time a Continuable function returns a falsey value
* against a set of values, otherwise returns true. Meant to be used with
* delegating yield (yield*).
*
* @param Array array
* The set of values to map over.
* @param Continuable fn
* The function to be applied to each value.
* @return Boolean
*/
exports.every = function* every(array, cb, receiver){
for (let i = 0; i < array.length; i++) {
if (i in array && !(yield cb.call(receiver, array[i], i, array))) {
return false;
}
}
return true;
};
"use strict";
const fs = require('fs');
const path = require('path');
const gen = require('./continuable-generators');
const readdir = gen.makeContinuable(fs.readdir);
const stat = gen.makeContinuable(fs.stat);
function* fulldir(dir){
return (yield readdir(dir)).map(function(child){
return path.resolve(dir, child);
});
}
function* statdir(dir){
const children = yield* fulldir(dir);
return yield* gen.map(children, function(item){
return stat(item);
});
}
function* sizedir(dir){
const children = (yield* statdir(dir)).filter(function(child){
return child.isFile();
});
return children.reduce(function(total, child){
return total + child.size;
}, 0);
}
gen.run(function*(){
console.log(yield* sizedir('.'));
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment