/*! * async.js * Copyright(c) 2010 Fabian Jakobs * MIT Licensed */ define(function(require, exports, module) { var STOP = exports.STOP = {} exports.Generator = function(source) { if (typeof source == "function") this.source = { next: source } else this.source = source } ;(function() { this.next = function(callback) { this.source.next(callback) } this.map = function(mapper) { if (!mapper) return this mapper = makeAsync(1, mapper) var source = this.source this.next = function(callback) { source.next(function(err, value) { if (err) callback(err) else { mapper(value, function(err, value) { if (err) callback(err) else callback(null, value) }) } }) } return new this.constructor(this) } this.filter = function(filter) { if (!filter) return this filter = makeAsync(1, filter) var source = this.source this.next = function(callback) { source.next(function handler(err, value) { if (err) callback(err) else { filter(value, function(err, takeIt) { if (err) callback(err) else if (takeIt) callback(null, value) else source.next(handler) }) } }) } return new this.constructor(this) } this.slice = function(begin, end) { var count = -1 if (!end || end < 0) var end = Infinity var source = this.source this.next = function(callback) { source.next(function handler(err, value) { count++ if (err) callback(err) else if (count >= begin && count < end) callback(null, value) else if (count >= end) callback(STOP) else source.next(handler) }) } return new this.constructor(this) } this.reduce = function(reduce, initialValue) { reduce = makeAsync(3, reduce) var index = 0 var done = false var previousValue = initialValue var source = this.source this.next = function(callback) { if (done) return callback(STOP) if (initialValue === undefined) { source.next(function(err, currentValue) { if (err) return callback(err, previousValue) previousValue = currentValue reduceAll() }) } else reduceAll() function reduceAll() { source.next(function handler(err, currentValue) { if (err) { done = true if (err == STOP) return callback(null, previousValue) else return(err) } reduce(previousValue, currentValue, index++, function(err, value) { previousValue = value source.next(handler) }) }) } } return new this.constructor(this) } this.forEach = this.each = function(fn) { fn = makeAsync(1, fn) var source = this.source this.next = function(callback) { source.next(function handler(err, value) { if (err) callback(err) else { fn(value, function(err) { callback(err, value) }) } }) } return new this.constructor(this) } this.some = function(condition) { condition = makeAsync(1, condition) var source = this.source var done = false this.next = function(callback) { if (done) return callback(STOP) source.next(function handler(err, value) { if (err) return callback(err) condition(value, function(err, result) { if (err) { done = true if (err == STOP) callback(null, false) else callback(err) } else if (result) { done = true callback(null, true) } else source.next(handler) }) }) } return new this.constructor(this) } this.every = function(condition) { condition = makeAsync(1, condition) var source = this.source var done = false this.next = function(callback) { if (done) return callback(STOP) source.next(function handler(err, value) { if (err) return callback(err) condition(value, function(err, result) { if (err) { done = true if (err == STOP) callback(null, true) else callback(err) } else if (!result) { done = true callback(null, false) } else source.next(handler) }) }) } return new this.constructor(this) } this.call = function(context) { var source = this.source return this.map(function(fn, next) { fn = makeAsync(0, fn, context) fn.call(context, function(err, value) { next(err, value) }) }) } this.concat = function(generator) { var generators = [this] generators.push.apply(generators, arguments) var index = 0 var source = generators[index++] return new this.constructor(function(callback) { source.next(function handler(err, value) { if (err) { if (err == STOP) { source = generators[index++] if (!source) return callback(STOP) else return source.next(handler) } else return callback(err) } else return callback(null, value) }) }) } this.zip = function(generator) { var generators = [this] generators.push.apply(generators, arguments) return new this.constructor(function(callback) { exports.list(generators) .map(function(gen, next) { gen.next(next) }) .toArray(callback) }) } this.expand = function(inserter, constructor) { if (!inserter) return this var inserter = makeAsync(1, inserter) var constructor = constructor || this.constructor var source = this.source; var spliced = null; return new constructor(function next(callback) { if (!spliced) { source.next(function(err, value) { if (err) return callback(err) inserter(value, function(err, toInsert) { if (err) return callback(err) spliced = toInsert next(callback) }) }) } else { spliced.next(function(err, value) { if (err == STOP) { spliced = null return next(callback) } else if (err) return callback(err) callback(err, value) }) } }) } this.sort = function(compare) { var self = this var arrGen this.next = function(callback) { if (arrGen) return arrGen.next(callback) self.toArray(function(err, arr) { if (err) callback(err) else { arrGen = exports.list(arr.sort(compare)) arrGen.next(callback) } }) } return new this.constructor(this) } this.join = function(separator) { return this.$arrayOp(Array.prototype.join, separator !== undefined ? [separator] : null) } this.reverse = function() { return this.$arrayOp(Array.prototype.reverse) } this.$arrayOp = function(arrayMethod, args) { var self = this var i = 0 this.next = function(callback) { if (i++ > 0) return callback(STOP) self.toArray(function(err, arr) { if (err) callback(err, "") else { if (args) callback(null, arrayMethod.apply(arr, args)) else callback(null, arrayMethod.call(arr)) } }) } return new this.constructor(this) } this.end = function(breakOnError, callback) { if (!callback) { callback = arguments[0] breakOnError = true } var source = this.source var last var lastError source.next(function handler(err, value) { if (err) { if (err == STOP) callback && callback(lastError, last) else if (!breakOnError) { lastError = err source.next(handler) } else callback && callback(err, value) } else { last = value source.next(handler) } }) } this.toArray = function(breakOnError, callback) { if (!callback) { callback = arguments[0] breakOnError = true } var values = [] var errors = [] var source = this.source source.next(function handler(err, value) { if (err) { if (err == STOP) { if (breakOnError) return callback(null, values) else { errors.length = values.length return callback(errors, values) } } else { if (breakOnError) return callback(err) else errors[values.length] = err } } values.push(value) source.next(handler) }) } }).call(exports.Generator.prototype) var makeAsync = exports.makeAsync = function(args, fn, context) { if (fn.length > args) return fn else { return function() { var value var next = arguments[args] try { value = fn.apply(context || this, arguments) } catch(e) { return next(e) } next(null, value) } } } exports.list = function(arr, construct) { var construct = construct || exports.Generator var i = 0 var len = arr.length return new construct(function(callback) { if (i < len) callback(null, arr[i++]) else callback(STOP) }) } exports.values = function(map, construct) { var values = [] for (var key in map) values.push(map[key]) return exports.list(values, construct) } exports.keys = function(map, construct) { var keys = [] for (var key in map) keys.push(key) return exports.list(keys, construct) } /* * range([start,] stop[, step]) -> generator of integers * * Return a generator containing an arithmetic progression of integers. * range(i, j) returns [i, i+1, i+2, ..., j-1] start (!) defaults to 0. * When step is given, it specifies the increment (or decrement). */ exports.range = function(start, stop, step, construct) { var construct = construct || exports.Generator start = start || 0 step = step || 1 if (stop === undefined || stop === null) stop = step > 0 ? Infinity : -Infinity var value = start return new construct(function(callback) { if (step > 0 && value >= stop || step < 0 && value <= stop) callback(STOP) else { var current = value value += step callback(null, current) } }) } exports.concat = function(first, varargs) { if (arguments.length > 1) return first.concat.apply(first, Array.prototype.slice.call(arguments, 1)) else return first } exports.zip = function(first, varargs) { if (arguments.length > 1) return first.zip.apply(first, Array.prototype.slice.call(arguments, 1)) else return first.map(function(item, next) { next(null, [item]) }) } exports.plugin = function(members, constructors) { if (members) { for (var key in members) { exports.Generator.prototype[key] = members[key] } } if (constructors) { for (var key in constructors) { exports[key] = constructors[key] } } } })