├── .gitignore ├── .travis.yml ├── Makefile ├── README.md ├── make └── build.js ├── package.json ├── src ├── copyright.js ├── ender.js └── valentine.js ├── tests ├── index.html └── tests.js ├── valentine.js └── valentine.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 0.10 5 | 6 | notifications: 7 | email: 8 | - dustin@dustindiaz.com 9 | 10 | script: make test 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: boosh test 2 | boosh: 3 | node make/build.js 4 | test: 5 | node tests/tests.js -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | \ / _. | _ ._ _|_ o ._ _ 2 | \/ (_| | (/_ | | |_ | | | (/_ 3 | 4 | [![Build Status](https://secure.travis-ci.org/ded/valentine.png)](http://travis-ci.org/ded/valentine) 5 | JavaScript's Sister, and protector — inspired by Underscore; Valentine provides you with type checking, functional iterators, and common utility helpers such as waterfalls, queues, and parallels; all utilizing native JavaScript methods for optimal speed. 6 | 7 | ## Deprecation notice 8 | 9 | As of version `2.0.0` — Valentine no longer supports `<= IE8` and `<= Safari 4`. It's been real, but time to move on. To access this level of support, use the [1.8 tag](https://github.com/ded/valentine/tree/v1.8.0). 10 | 11 | ### Browser usage: 12 | 13 | ``` html 14 | 15 | 20 | ``` 21 | 22 | ### Node users 23 | 24 | ``` sh 25 | npm install valentine 26 | ``` 27 | 28 | ``` js 29 | var v = require('valentine') 30 | 31 | // showcase object style 32 | v(['a', 'b', 'c']).map(function (letter) { 33 | return letter.toUpperCase() 34 | }).join(' '); // => 'A B C' 35 | ``` 36 | 37 | ## API 38 | 39 | 40 |

iterators

41 | 42 | * v.each(array || object, callback[, scope]) => void 43 | * v.map(array || object, callback[, scope]) => array 44 | * v.every(array || object, *callback[, scope]) => boolean 45 | * v.some(array || object, *callback[, scope]) => boolean 46 | * v.filter(array || object, *callback[, scope]) => array || object 47 | 48 | * v.reject(ar, *callback[, scope]) 49 | * v.indexOf(ar, item[, start]) 50 | * v.lastIndexOf(ar, item[, start]) 51 | * v.reduce(ar, **callback, memo[, scope]) 52 | * v.reduceRight(ar, **callback, memo[, scope]) 53 | 54 | *`callback` is defined as: 55 | 56 | ``` js 57 | // when array 58 | function callback(item, index, array) { 59 | 60 | } 61 | // when object 62 | function callback(key, value, object) { 63 | 64 | } 65 | ``` 66 | 67 | **`calback` is defined as: 68 | 69 | ``` js 70 | function callback(memo, item, index, array) { 71 | 72 | } 73 | ``` 74 | 75 | ### utility 76 | 77 | * v.extend(obj[, obj2[, obj3[...]]]) => object 78 | * v.merge(ar1, ar2) => array (ar1 modified) 79 | * v.pluck(array||object, key) => array 80 | * v.toArray(sparse) => array (duh) 81 | * v.size(array) => number 82 | * v.find(array, key) => *value 83 | * v.compact(array) => array 84 | * v.flatten(array) => array 85 | * v.uniq(array) => array 86 | * v.first(array) => *value 87 | * v.last(array) => *value 88 | * v.keys(object) => array 89 | * v.values(object) => array 90 | * v.trim(string) => string 91 | * v.bind(scope, fn, [curried args]) => function 92 | * v.curry(fn, [curried args]) => function 93 | * v.inArray(array, needle) => boolean 94 | * v.memo(fn, hasher) => function 95 | 96 | ``` js 97 | // use memo to cache expensive methods 98 | var getAllTheDom = v.memo(function () { 99 | return v(document.getElementsByTagName('*')).toArray() 100 | }) 101 | getAllTheDom().each(modifier) 102 | ``` 103 | 104 | #### parallel api 105 | 106 | * v.parallel([fn args]) => void 107 | 108 | ``` js 109 | v.parallel( 110 | function (fn) { 111 | getTimeline(function (e, timeline) { 112 | fn(e, timeline) 113 | }) 114 | } 115 | , function (fn) { 116 | getUser(function (e, user) { 117 | fn(e, user) 118 | }) 119 | } 120 | , function (e, timeline, user) { 121 | if (e) return console.log(e) 122 | ok(timeline == 'one', 'first result is "one"') 123 | ok(user == 'two', 'second result is "two"') 124 | } 125 | ) 126 | ``` 127 | 128 | #### waterfall api 129 | 130 | * v.waterfall([fn args]) 131 | * v.waterfall([fn1, fn2<, fn3>], callback) 132 | 133 | ``` js 134 | v.waterfall( 135 | function (callback) { 136 | callback(null, 'one', 'two') 137 | } 138 | , function (a, b, callback) { 139 | console.log(a == 'one') 140 | console.log(b == 'two') 141 | callback(null, 'three') 142 | } 143 | , function (c, callback) { 144 | console.log(c == 'three') 145 | callback(null, 'final result') 146 | } 147 | , function (err, result) { 148 | console.log(!!err == false) 149 | console.log(result == 'final result') 150 | } 151 | ) 152 | ``` 153 | 154 | #### series api 155 | * similar to `waterfall` except passing along args to next function is not a concern 156 | * v.series([fn1, fn2<, fn3>], callback) 157 | 158 | ``` js 159 | v.series([ 160 | function (callback) { 161 | setTimeout(callback, 2000) 162 | } 163 | , function (callback) { 164 | process.nextTick(callback) 165 | } 166 | , function (callback) { 167 | callback(null) 168 | }] 169 | , function (err) { 170 | console.log('done') 171 | } 172 | ) 173 | ``` 174 | 175 | #### Queue api 176 | 177 | * v.queue([fn args]) 178 | 179 | ``` js 180 | var it = v.queue( 181 | function () { 182 | console.log('one') 183 | it.next() 184 | } 185 | , function () { 186 | console.log('two') 187 | it.next() 188 | } 189 | , function () { 190 | console.log('three') 191 | } 192 | ) 193 | it.next() 194 | ``` 195 | 196 | #### throttle, debounce, throttleDebounce 197 | 198 | * v.throttle(ms, fn, opt_scope) => function 199 | * v.debounce(ms, fn, opt_scope) => function 200 | * v.throttleDebounce(throttleMs, debounceMs, fn, opt_scope) => function 201 | 202 | ``` js 203 | window.onscroll = v.throttle(50, function (e) { 204 | // expensive scroll function 205 | }) 206 | 207 | window.mousemove = v.debounce(500, function (e) { 208 | // user has paused momentarily 209 | }) 210 | 211 | textarea.onkeypress = v.throttleDebounce(20000, 1000, function () { 212 | // autosave(this.value) 213 | // called after 1s if not called again within 1s 214 | // but guaranteed to be called within 20s 215 | }) 216 | ``` 217 | 218 | #### type checking 219 | Each method returns a boolean 220 | 221 | * v.is.func(o) 222 | * v.is.string(o) 223 | * v.is.element(o) 224 | * v.is.array(o) 225 | * v.is.arrLike(o) 226 | * v.is.num(o) 227 | * v.is.bool(o) 228 | * v.is.args(o) 229 | * v.is.empty(o) 230 | * v.is.date(o) 231 | * v.is.nan(o) 232 | * v.is.nil(o) 233 | * v.is.undef(o) 234 | * is.regexp(o) 235 | * v.is.obj(o) 236 | 237 | ### Object Style 238 | 239 | ``` js 240 | v(['a', 'b', 'c']).map(function (letter) { 241 | return letter.toUpperCase() 242 | }); // => ['A', 'B', 'C'] 243 | ``` 244 | 245 | ### Chains 246 | 247 | ``` js 248 | v(['a', 'b', [['c']], 0, false,,,null,['a', 'b', 'c']]) 249 | .chain().flatten().compact().uniq().map(function (letter) { 250 | return letter.toUpperCase() 251 | }).value(); // => ['A', 'B', 'C'] 252 | ``` 253 | 254 | ## Ender Support 255 | 256 | ``` sh 257 | ender add valentine 258 | ``` 259 | 260 | ``` js 261 | 262 | // available as a top level method on `$` 263 | $.v(['a', ['virus'], 'b', 'c']).reject(function (el, i) { 264 | return $.is.arr(el[i]) 265 | }) 266 | 267 | // top level methods in bridge 268 | $.each 269 | map 270 | merge 271 | extend 272 | toArray 273 | keys 274 | values 275 | trim 276 | bind 277 | curry 278 | parallel 279 | waterfall 280 | inArray 281 | queue 282 | ``` 283 | 284 | Or just require the valentine module 285 | 286 | ``` js 287 | !function (v) { 288 | v(['a', ['virus'], 'b', 'c']).reject(function (el, i) { 289 | return v.is.arr(el[i]) 290 | }) 291 | }(require('valentine')) 292 | ``` 293 | 294 | ## Developers 295 | Care to contribute? Make your edits to `src/valentine.js` and get your environment up and running 296 | 297 | ``` sh 298 | npm install 299 | make 300 | make test 301 | open tests/index.html 302 | ``` 303 | 304 | *Happy iterating*! 305 | -------------------------------------------------------------------------------- /make/build.js: -------------------------------------------------------------------------------- 1 | require('smoosh').config({ 2 | "JAVASCRIPT": { 3 | "DIST_DIR": "./" 4 | , "valentine": [ 5 | "./src/copyright.js" 6 | , "./src/valentine.js" 7 | ] 8 | } 9 | , "JSHINT_OPTS": { 10 | "boss": true 11 | , "forin": false 12 | , "laxcomma": true 13 | , "curly": false 14 | , "debug": false 15 | , "devel": false 16 | , "evil": false 17 | , "regexp": false 18 | , "undef": false 19 | , "sub": true 20 | , "white": false 21 | , "indent": 2 22 | , "asi": true 23 | , "laxbreak": true 24 | } 25 | }).run().build().analyze() -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "valentine", 3 | "description": "JavaScript’s Functional Sister. Utilitiy, Iterators, type checking", 4 | "version": "2.1.4", 5 | "homepage": "https://github.com/ded/valentine", 6 | "author": "Dustin Diaz (http://dustindiaz.com)", 7 | "license": "MIT", 8 | "keywords": [ 9 | "ender", 10 | "functional", 11 | "iteration", 12 | "type checking", 13 | "base" 14 | ], 15 | "main": "./valentine.js", 16 | "ender": "./src/ender.js", 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/ded/valentine.git" 20 | }, 21 | "devDependencies": { 22 | "sink-test": ">= 1.0.1", 23 | "smoosh": "0.4.x" 24 | }, 25 | "scripts": { 26 | "test": "make test" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/copyright.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Valentine: JavaScript's functional Sister 3 | * (c) Dustin Diaz 2014 4 | * https://github.com/ded/valentine License MIT 5 | */ 6 | -------------------------------------------------------------------------------- /src/ender.js: -------------------------------------------------------------------------------- 1 | var v = require('valentine') 2 | ender.ender(v) 3 | ender.ender({ 4 | merge: v.merge 5 | , extend: v.extend 6 | , each: v.each 7 | , map: v.map 8 | , toArray: v.toArray 9 | , keys: v.keys 10 | , values: v.values 11 | , trim: v.trim 12 | , bind: v.bind 13 | , curry: v.curry 14 | , parallel: v.parallel 15 | , waterfall: v.waterfall 16 | , inArray: v.inArray 17 | , queue: v.queue 18 | }) 19 | -------------------------------------------------------------------------------- /src/valentine.js: -------------------------------------------------------------------------------- 1 | (function (name, context, definition) { 2 | if (typeof module != 'undefined') module.exports = definition() 3 | else if (typeof define == 'function') define(definition) 4 | else context[name] = context['v'] = definition() 5 | })('valentine', this, function () { 6 | 7 | var ap = [] 8 | , hasOwn = Object.prototype.hasOwnProperty 9 | , n = null 10 | , slice = ap.slice 11 | 12 | var iters = { 13 | each: function (a, fn, scope) { 14 | ap.forEach.call(a, fn, scope) 15 | } 16 | 17 | , map: function (a, fn, scope) { 18 | return ap.map.call(a, fn, scope) 19 | } 20 | 21 | , some: function (a, fn, scope) { 22 | return a.some(fn, scope) 23 | } 24 | 25 | , every: function (a, fn, scope) { 26 | return a.every(fn, scope) 27 | } 28 | 29 | , filter: function (a, fn, scope) { 30 | return a.filter(fn, scope) 31 | } 32 | , indexOf: function (a, el, start) { 33 | return a.indexOf(el, isFinite(start) ? start : 0) 34 | } 35 | 36 | , lastIndexOf: function (a, el, start) { 37 | return a.lastIndexOf(el, isFinite(start) ? start : a.length) 38 | } 39 | 40 | , reduce: function (o, i, m, c) { 41 | return ap.reduce.call(o, v.bind(c, i), m); 42 | } 43 | 44 | , reduceRight: function (o, i, m, c) { 45 | return ap.reduceRight.call(o, v.bind(c, i), m) 46 | } 47 | 48 | , find: function (obj, iterator, context) { 49 | var result 50 | iters.some(obj, function (value, index, list) { 51 | if (iterator.call(context, value, index, list)) { 52 | result = value 53 | return true 54 | } 55 | }) 56 | return result 57 | } 58 | 59 | , reject: function (a, fn, scope) { 60 | var r = [] 61 | for (var i = 0, j = 0, l = a.length; i < l; i++) { 62 | if (i in a) { 63 | if (fn.call(scope, a[i], i, a)) { 64 | continue; 65 | } 66 | r[j++] = a[i] 67 | } 68 | } 69 | return r 70 | } 71 | 72 | , size: function (a) { 73 | return o.toArray(a).length 74 | } 75 | 76 | , compact: function (a) { 77 | return iters.filter(a, function (value) { 78 | return !!value 79 | }) 80 | } 81 | 82 | , flatten: function (a) { 83 | return iters.reduce(a, function (memo, value) { 84 | if (is.arr(value)) { 85 | return memo.concat(iters.flatten(value)) 86 | } 87 | memo[memo.length] = value 88 | return memo 89 | }, []) 90 | } 91 | 92 | , uniq: function (ar, opt_iterator) { 93 | if (ar === null) return [] 94 | var a = [], seen = [] 95 | for (var i = 0, length = ar.length; i < length; i++) { 96 | var value = ar[i] 97 | if (opt_iterator) value = opt_iterator(value, i, ar) 98 | if (!iters.inArray(seen, value)) { 99 | seen.push(value) 100 | a.push(ar[i]) 101 | } 102 | } 103 | return a 104 | } 105 | 106 | , merge: function (one, two) { 107 | var i = one.length, j = 0, l 108 | if (isFinite(two.length)) { 109 | for (l = two.length; j < l; j++) { 110 | one[i++] = two[j] 111 | } 112 | } else { 113 | while (two[j] !== undefined) { 114 | one[i++] = two[j++] 115 | } 116 | } 117 | one.length = i 118 | return one 119 | } 120 | 121 | , inArray: function (ar, needle) { 122 | return !!~iters.indexOf(ar, needle) 123 | } 124 | 125 | , memo: function (fn, hasher) { 126 | var store = {} 127 | hasher || (hasher = function (v) { 128 | return v 129 | }) 130 | return function () { 131 | var key = hasher.apply(this, arguments) 132 | return hasOwn.call(store, key) ? store[key] : (store[key] = fn.apply(this, arguments)) 133 | } 134 | } 135 | } 136 | 137 | var is = { 138 | fun: function (f) { 139 | return typeof f === 'function' 140 | } 141 | 142 | , str: function (s) { 143 | return typeof s === 'string' 144 | } 145 | 146 | , ele: function (el) { 147 | return !!(el && el.nodeType && el.nodeType == 1) 148 | } 149 | 150 | , arr: function (ar) { 151 | return ar instanceof Array 152 | } 153 | 154 | , arrLike: function (ar) { 155 | return (ar && ar.length && isFinite(ar.length)) 156 | } 157 | 158 | , num: function (n) { 159 | return typeof n === 'number' 160 | } 161 | 162 | , bool: function (b) { 163 | return (b === true) || (b === false) 164 | } 165 | 166 | , args: function (a) { 167 | return !!(a && hasOwn.call(a, 'callee')) 168 | } 169 | 170 | , emp: function (o) { 171 | var i = 0 172 | return is.arr(o) ? o.length === 0 : 173 | is.obj(o) ? (function () { 174 | for (var _ in o) { 175 | i++ 176 | break; 177 | } 178 | return (i === 0) 179 | }()) : 180 | o === '' 181 | } 182 | 183 | , dat: function (d) { 184 | return !!(d && d.getTimezoneOffset && d.setUTCFullYear) 185 | } 186 | 187 | , reg: function (r) { 188 | return !!(r && r.test && r.exec && (r.ignoreCase || r.ignoreCase === false)) 189 | } 190 | 191 | , nan: function (n) { 192 | return n !== n 193 | } 194 | 195 | , nil: function (o) { 196 | return o === n 197 | } 198 | 199 | , und: function (o) { 200 | return typeof o === 'undefined' 201 | } 202 | 203 | , def: function (o) { 204 | return typeof o !== 'undefined' 205 | } 206 | 207 | , obj: function (o) { 208 | return o instanceof Object && !is.fun(o) && !is.arr(o) 209 | } 210 | } 211 | 212 | // nicer looking aliases 213 | is.empty = is.emp 214 | is.date = is.dat 215 | is.regexp = is.reg 216 | is.element = is.ele 217 | is.array = is.arr 218 | is.string = is.str 219 | is.undef = is.und 220 | is.func = is.fun 221 | 222 | var o = { 223 | each: function each(a, fn, scope) { 224 | is.arrLike(a) ? 225 | iters.each(a, fn, scope) : (function () { 226 | for (var k in a) { 227 | hasOwn.call(a, k) && fn.call(scope, k, a[k], a) 228 | } 229 | }()) 230 | } 231 | 232 | , map: function map(a, fn, scope) { 233 | var r = [], i = 0 234 | return is.arrLike(a) ? 235 | iters.map(a, fn, scope) : !function () { 236 | for (var k in a) { 237 | hasOwn.call(a, k) && (r[i++] = fn.call(scope, k, a[k], a)) 238 | } 239 | }() && r 240 | } 241 | 242 | , some: function some(a, fn, scope) { 243 | if (is.arrLike(a)) return iters.some(a, fn, scope) 244 | for (var k in a) { 245 | if (hasOwn.call(a, k) && fn.call(scope, k, a[k], a)) { 246 | return true 247 | } 248 | } 249 | return false 250 | 251 | } 252 | 253 | , every: function every(a, fn, scope) { 254 | if (is.arrLike(a)) return iters.every(a, fn, scope) 255 | for (var k in a) { 256 | if (!(hasOwn.call(a, k) && fn.call(scope, k, a[k], a))) { 257 | return false 258 | } 259 | } 260 | return true 261 | } 262 | 263 | , filter: function filter(a, fn, scope) { 264 | var r = {}, k 265 | if (is.arrLike(a)) return iters.filter(a, fn, scope) 266 | for (k in a) { 267 | if (hasOwn.call(a, k) && fn.call(scope, k, a[k], a)) { 268 | r[k] = a[k] 269 | } 270 | } 271 | return r 272 | } 273 | 274 | , pluck: function pluck(a, k) { 275 | return is.arrLike(a) ? 276 | iters.map(a, function (el) { 277 | return el[k] 278 | }) : 279 | o.map(a, function (_, v) { 280 | return v[k] 281 | }) 282 | } 283 | 284 | , toArray: function toArray(a) { 285 | if (!a) return [] 286 | 287 | if (is.arr(a)) return a 288 | 289 | if (a.toArray) return a.toArray() 290 | 291 | if (is.args(a)) return slice.call(a) 292 | 293 | return iters.map(a, function (k) { 294 | return k 295 | }) 296 | } 297 | 298 | , first: function first(a) { 299 | return a[0] 300 | } 301 | 302 | , last: function last(a) { 303 | return a[a.length - 1] 304 | } 305 | 306 | , keys: Object.keys 307 | , values: function (ob) { 308 | return o.map(ob, function (_, v) { 309 | return v 310 | }) 311 | } 312 | 313 | , extend: function extend() { 314 | // based on jQuery deep merge 315 | var options, name, src, copy, clone 316 | , target = arguments[0], i = 1, length = arguments.length 317 | 318 | for (; i < length; i++) { 319 | if ((options = arguments[i]) !== n) { 320 | // Extend the base object 321 | for (name in options) { 322 | src = target[name] 323 | copy = options[name] 324 | if (target === copy) { 325 | continue; 326 | } 327 | if (copy && (is.obj(copy))) { 328 | clone = src && is.obj(src) ? src : {} 329 | target[name] = o.extend(clone, copy); 330 | } else if (copy !== undefined) { 331 | target[name] = copy 332 | } 333 | } 334 | } 335 | } 336 | return target 337 | } 338 | 339 | , trim: function (s) { 340 | return s.trim() 341 | } 342 | 343 | , bind: function (scope, fn) { 344 | var args = arguments.length > 2 ? slice.call(arguments, 2) : null 345 | return function () { 346 | return fn.apply(scope, args ? args.concat(slice.call(arguments)) : arguments) 347 | } 348 | } 349 | 350 | , curry: function curry(fn) { 351 | if (arguments.length == 1) return fn 352 | var args = slice.call(arguments, 1) 353 | return function () { 354 | return fn.apply(null, args.concat(slice.call(arguments))) 355 | } 356 | } 357 | 358 | , parallel: function parallel(fns, callback) { 359 | var args = o.toArray(arguments) 360 | , len = 0 361 | , returns = [] 362 | , flattened = [] 363 | 364 | if (is.arr(fns) && fns.length === 0 || (is.fun(fns) && args.length === 1)) throw new TypeError('Empty parallel array') 365 | if (!is.arr(fns)) { 366 | callback = args.pop() 367 | fns = args 368 | } 369 | 370 | iters.each(fns, function (el, i) { 371 | el(function () { 372 | var a = o.toArray(arguments) 373 | , e = a.shift() 374 | if (e) return callback(e) 375 | returns[i] = a 376 | if (fns.length == ++len) { 377 | returns.unshift(n) 378 | 379 | iters.each(returns, function (r) { 380 | flattened = flattened.concat(r) 381 | }) 382 | 383 | callback.apply(n, flattened) 384 | } 385 | }) 386 | }) 387 | } 388 | 389 | , waterfall: function waterfall(fns, callback) { 390 | var args = o.toArray(arguments) 391 | 392 | if (is.arr(fns) && fns.length === 0 || (is.fun(fns) && args.length === 1)) throw new TypeError('Empty waterfall array') 393 | if (!is.arr(fns)) { 394 | callback = args.pop() 395 | fns = args 396 | } 397 | 398 | (function f() { 399 | var args = o.toArray(arguments) 400 | if (!args.length) args.push(null) // allow callbacks with no args as passable non-errored functions 401 | args.push(f) 402 | var err = args.shift() 403 | if (!err && fns.length) fns.shift().apply(n, args) 404 | else { 405 | args.pop() 406 | args.unshift(err) 407 | callback.apply(n, args) 408 | } 409 | }(n)) 410 | } 411 | 412 | , series: function (tasks, callback) { 413 | o.waterfall(tasks.map(function (task) { 414 | return function (f) { 415 | task(function (err) { 416 | f(err) 417 | }) 418 | } 419 | }), callback) 420 | } 421 | 422 | , queue: function queue(ar) { 423 | return new Queue(is.arrLike(ar) ? ar : o.toArray(arguments)) 424 | } 425 | 426 | , debounce: function debounce(wait, fn, opt_scope) { 427 | var timeout 428 | function caller() { 429 | var args = arguments 430 | , context = opt_scope || this 431 | function later() { 432 | timeout = null 433 | fn.apply(context, args) 434 | } 435 | clearTimeout(timeout) 436 | timeout = setTimeout(later, wait) 437 | } 438 | 439 | // cancelation method 440 | caller.cancel = function debounceCancel() { 441 | clearTimeout(timeout) 442 | timeout = null 443 | } 444 | 445 | return caller 446 | } 447 | 448 | , throttle: function throttle(wait, fn, opt_scope, head) { 449 | var timeout 450 | var origHead = head 451 | return function throttler() { 452 | var context = opt_scope || this 453 | , args = arguments 454 | if (head) { 455 | fn.apply(context, args) 456 | head = false 457 | return 458 | } 459 | if (!timeout) { 460 | timeout = setTimeout(function throttleTimeout() { 461 | fn.apply(context, args) 462 | timeout = null 463 | head = origHead 464 | }, 465 | wait 466 | ) 467 | } 468 | } 469 | } 470 | 471 | , throttleDebounce: function (throttleMs, debounceMs, fn, opt_scope) { 472 | var args 473 | , context 474 | , debouncer 475 | , throttler 476 | 477 | function caller() { 478 | args = arguments 479 | context = opt_scope || this 480 | 481 | clearTimeout(debouncer) 482 | debouncer = setTimeout(function () { 483 | clearTimeout(throttler) 484 | throttler = null 485 | fn.apply(context, args) 486 | }, debounceMs) 487 | 488 | if (!throttler) { 489 | throttler = setTimeout(function () { 490 | clearTimeout(debouncer) 491 | throttler = null 492 | fn.apply(context, args) 493 | }, throttleMs) 494 | } 495 | } 496 | 497 | // cancelation method 498 | caller.cancel = function () { 499 | clearTimeout(debouncer) 500 | clearTimeout(throttler) 501 | throttler = null 502 | } 503 | 504 | return caller 505 | } 506 | } 507 | 508 | function Queue (a) { 509 | this.values = a 510 | this.index = 0 511 | } 512 | 513 | Queue.prototype.next = function () { 514 | this.index < this.values.length && this.values[this.index++]() 515 | return this 516 | } 517 | 518 | function v(a, scope) { 519 | return new Valentine(a, scope) 520 | } 521 | 522 | function aug(o, o2) { 523 | for (var k in o2) o[k] = o2[k] 524 | } 525 | 526 | aug(v, iters) 527 | aug(v, o) 528 | v.is = is 529 | 530 | v.v = v // vainglory 531 | 532 | // peoples like the object style 533 | function Valentine(a, scope) { 534 | this.val = a 535 | this._scope = scope || n 536 | this._chained = 0 537 | } 538 | 539 | v.each(v.extend({}, iters, o), function (name, fn) { 540 | Valentine.prototype[name] = function () { 541 | var a = v.toArray(arguments) 542 | a.unshift(this.val) 543 | var ret = fn.apply(this._scope, a) 544 | this.val = ret 545 | return this._chained ? this : ret 546 | } 547 | }) 548 | 549 | // people like chaining 550 | aug(Valentine.prototype, { 551 | chain: function () { 552 | this._chained = 1 553 | return this 554 | } 555 | , value: function () { 556 | return this.val 557 | } 558 | }) 559 | 560 | return v 561 | }); 562 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Valentine Unit Tests 5 | 6 | 7 | 8 | 9 | 10 | 11 |

Valentine Unit Tests

12 |
    13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/tests.js: -------------------------------------------------------------------------------- 1 | if (typeof module !== 'undefined' && module.exports) { 2 | var s = require('sink-test') 3 | // these become globals to make the tests IE-friendly due to stupid hoisting 4 | start = s.start 5 | sink = s.sink 6 | v = require('../src/valentine') 7 | } 8 | 9 | sink.timeout = 3000 10 | 11 | sink('Arrays', function(test, ok, before, after) { 12 | test('each', 3, function () { 13 | v.each(['a', 'b'], function (el, i, ar) { 14 | ok(el == ar[i], 'each of arrays ' + i); 15 | }); 16 | v.each({ foo: 'bar' }, function (k, v, ar) { 17 | ok(v == ar[k], 'each of objects'); 18 | }); 19 | }); 20 | 21 | test('map', 2, function () { 22 | var m = v.map(['a', 'b'], function (el) { 23 | return el.toUpperCase(); 24 | }); 25 | 26 | ok(m[0] === 'A' && m[1] === 'B', 'map of arrays'); 27 | 28 | var om = v.map({ 29 | foo: 'bar', 30 | baz: 'thunk' 31 | }, function (k, v) { 32 | return v; 33 | }); 34 | 35 | ok(om[0] === 'bar' && om[1] === 'thunk', 'map of objects'); 36 | 37 | }); 38 | 39 | test('every', function (done) { 40 | var a1 = ['a', 'a', 'a'] 41 | var a2 = ['a', 'a', 'b'] 42 | var o = { 43 | a: 'b' 44 | , c: 'd' 45 | , foo: 'foo' 46 | } 47 | 48 | ok(v.every(a1, function (el) { 49 | return el == 'a' 50 | }), 'all elements in array are "a"'); 51 | 52 | ok(!v.every(a2, function (el) { 53 | return el == 'a' 54 | }), 'all elements in array are not "a"') 55 | 56 | ok(v.every(o, function (key, val) { 57 | return true 58 | })) 59 | 60 | ok(v.every(o, function (key, val) { 61 | return key.match(/\w+/) 62 | })) 63 | 64 | ok(!v.every(o, function (key, val) { 65 | return false 66 | })) 67 | 68 | done() 69 | }) 70 | 71 | test('some', function (done) { 72 | var a1 = ['a', 'a', 'a'] 73 | var a2 = ['a', 'a', 'b'] 74 | var o = { 75 | a: 'b' 76 | , c: 'd' 77 | , foo: 'foo' 78 | } 79 | 80 | ok(!v.some(a1, function (el) { 81 | return el == 'b' 82 | }), 'no elements in array have "b"') 83 | 84 | ok(v.some(a2, function (el) { 85 | return el == 'b' 86 | }), 'some elements in array have "b"') 87 | 88 | ok(v.some(o, function (key, val) { 89 | return key == 'a' 90 | })) 91 | 92 | ok(v.some(o, function (key, val) { 93 | return val == 'd' 94 | })) 95 | 96 | ok(v.some(o, function (key, val) { 97 | return val != 'e' 98 | })) 99 | 100 | ok(v.some(o, function (key, val) { 101 | return key == val 102 | })) 103 | done() 104 | }) 105 | 106 | test('filter', function (done) { 107 | var a = v.filter(['a', 'b', 'virus', 'c'], function (el) { 108 | return el !== 'virus'; 109 | }); 110 | var expected = ['a', 'b', 'c'] 111 | ok(v.every(expected, function (el, i) { 112 | return el == a[i]; 113 | }), 'filters out viruses') 114 | 115 | var o = { 116 | foo: 'bar', 117 | baz: 'thunk' 118 | } 119 | 120 | ok(JSON.stringify(v(o).filter(function (key, val) { 121 | return true 122 | })) == JSON.stringify(o), 'every item is filtered in') 123 | 124 | ok(JSON.stringify(v(o).filter(function (key, val) { 125 | return false 126 | })) == JSON.stringify({}), 'zero items are filtered in') 127 | 128 | var actual = v.filter(o, function (key, val) { 129 | return key == 'foo' 130 | }) 131 | ok(JSON.stringify(actual) == JSON.stringify({foo: 'bar'}), 'actual: ' + JSON.stringify(actual)) 132 | 133 | done() 134 | }) 135 | 136 | test('reject', 1, function () { 137 | var a = v.reject(['a', 'b', 'virus', 'c'], function (el) { 138 | return el === 'virus'; 139 | }); 140 | var expected = ['a', 'b', 'c']; 141 | ok(v.every(expected, function (el, i) { 142 | return el == a[i]; 143 | }), 'rejects out viruses'); 144 | }); 145 | 146 | test('indexOf', 2, function () { 147 | ok(v.indexOf(['a', 'b', 'c'], 'b') == 1, 'indexOf b == 1'); 148 | ok(v.indexOf(['x', 'y', 'z'], 'b') == -1, 'indexOf b == -1'); 149 | }); 150 | 151 | test('lastIndexOf', 2, function () { 152 | ok(v.lastIndexOf(['a', 'b', 'c'], 'c') == 2, 'indexOf c == 2'); 153 | ok(v.lastIndexOf(['x', 'y', 'z'], 'b') == -1, 'indexOf b == -1'); 154 | }); 155 | 156 | v.each(['reduce', 'reduceRight'], function (method, right) { 157 | test(method, 3, function() { 158 | var init = {}, scope = {} 159 | v[method]([5], function (memo, n, i, a) { 160 | ok(this === scope, method + ' iterator scope') 161 | ok(init === memo && a[i] === n, method + ' iterator signature') 162 | }, init, scope) 163 | 164 | var a = ['b', 'c'] 165 | ok(v[method](a, function (memo, n) { 166 | return memo + n 167 | }, 'a') === (right ? 'acb' : 'abc'), method + ' result') 168 | }) 169 | }) 170 | 171 | }) 172 | 173 | sink('Utility', function (test, ok, b, a, assert) { 174 | test('extend', 2, function () { 175 | var o = { 176 | foo: 'bar' 177 | }; 178 | var out = v.extend(o, { baz: 'thunk' }); 179 | ok(out.foo == 'bar', 'contains foo property'); 180 | ok(out.baz == 'thunk', 'contains baz property'); 181 | }); 182 | 183 | test('extend deep', 2, function () { 184 | 185 | 186 | var o = { 187 | foo: { 188 | baz: 'thunk' 189 | } 190 | , dog: { 191 | bag: 'of stuff' 192 | , junk: 'murr' 193 | } 194 | } 195 | var o2 = { 196 | foo: { 197 | bar: 'baz' 198 | } 199 | , cat: { 200 | bag: 'of more stuff' 201 | } 202 | , dog: { 203 | junk: 'not murr' 204 | } 205 | } 206 | var out = v.extend(o, o2) 207 | ok(v.is.def(out.foo.baz), 'contains baz property') 208 | ok(v.is.def(out.foo.bar), 'contains bar property') 209 | }) 210 | 211 | test('pluck', 2, function () { 212 | var arr = [ 213 | {a: 'foo'} 214 | , {a: 'bar'} 215 | , {a: 'baz'} 216 | ] 217 | , obj = { 218 | b: {a: 'foo'} 219 | , c: {a: 'bar'} 220 | , d: {a: 'baz'} 221 | } 222 | , expected = ['foo', 'bar', 'baz'] 223 | 224 | ok(v.every(v.pluck(arr, 'a'), function (el, i) { 225 | return el == expected[i] 226 | }), 'plucked foo bar baz from arr') 227 | 228 | ok(v.every(v.pluck(obj, 'a'), function (el, i) { 229 | return el == expected[i] 230 | }), 'plucked foo bar baz from obj') 231 | }) 232 | 233 | test('toArray', 1, function () { 234 | !function () { 235 | ok(v.toArray(arguments) instanceof Array, 'element collection is now an array'); 236 | }('a', 'b', 'c') 237 | }); 238 | 239 | test('size', 1, function () { 240 | ok(v.size(['a', 'b', 'c']) == 3, 'size is 3'); 241 | }); 242 | 243 | test('find', 1, function () { 244 | ok(v.find(['a', 'b', 'c'], function (el) { 245 | return el === 'a'; 246 | }) === 'a', 'found element "a"'); 247 | }); 248 | 249 | test('compact', 1, function () { 250 | ok(v.compact([,,,1,false,0,null,'']).length == 1, 'compacted [,,,1,false,0,null,""] to [1]'); 251 | }); 252 | 253 | test('flatten', 1, function () { 254 | var actual = v.flatten([['a', [[['b']], ['c']], 'd']]) 255 | , expected = ['a', 'b', 'c', 'd'] 256 | ok(v.every(actual, function (el, i) { 257 | return el == expected[i] 258 | }), 'flattened a really crappy looking array') 259 | }) 260 | 261 | test('uniq.array', 1, function () { 262 | var actual = v.uniq(['a', 'a', 'a', 'b', 'b', 'c']) 263 | , expected = ['a', 'b', 'c'] 264 | ok(v.every(actual, function (el, i) { 265 | return el == expected[i] 266 | }), "turned ['a', 'a', 'a', 'b', 'b', 'c'] into ['a', 'b', 'c']") 267 | }) 268 | 269 | test('uniq.object', 2, function () { 270 | var input = [ 271 | {user: 1}, 272 | {user: 2}, 273 | {user: 3}, 274 | {user: 1} 275 | ] 276 | var actual = v.uniq(input, function (item) { 277 | return item.user 278 | }) 279 | var expected = [ 280 | {user: 1}, 281 | {user: 2}, 282 | {user: 3} 283 | ] 284 | ok(actual.length === 3, 'filtered out duplicate object') 285 | ok(v.every(actual, function (el, i) { 286 | return el.user == expected[i].user 287 | }), "converted duplicate object array into unique object array") 288 | }) 289 | 290 | test('merge', 2, function () { 291 | // object style 292 | var actual = v(['a', 'b', 'c']).merge(['e', 'f', 'g']) 293 | , expected = ['a', 'b', 'c', 'e', 'f', 'g'] 294 | 295 | ok(v.every(expected, function (el, i) { 296 | return el == actual[i] 297 | }), "merged ['a', 'b', 'c'] and ['d', 'e', 'f']"); 298 | 299 | // functional style 300 | actual = v.merge(['a', 'b', 'c'], ['e', 'f', 'g']); 301 | ok(v.every(expected, function (el, i) { 302 | return el == actual[i]; 303 | }), "merged ['a', 'b', 'c'] and ['d', 'e', 'f']"); 304 | 305 | }) 306 | 307 | test('inArray', 4, function () { 308 | ok(v(['a', 'b', 'c']).inArray('b'), 'found b in ["a", "b", "c"]') 309 | ok(v.inArray(['a', 'b', 'c'], 'b'), 'found b in ["a", "b", "c"]') 310 | ok(!v(['a', 'b', 'c']).inArray('d'), 'did not find d in ["a", "b", "c"]') 311 | ok(!v.inArray(['a', 'b', 'c'], 'd'), 'did not find d in ["a", "b", "c"]') 312 | }) 313 | 314 | test('memoize', function (done) { 315 | var called = 0 316 | var add = v.memo(function (a, b) { 317 | ok(++called == 1, 'only calls memoized method once') 318 | return a + b 319 | }) 320 | assert.isFunction(add) 321 | var computed = add(1, 2) 322 | var expected = 3 323 | assert.equal(computed, expected, 'should be called once') 324 | var secondCall = add(1, 2) 325 | 326 | assert.notEqual(secondCall, 15, 'second call is not equal to 15') 327 | assert.equal(secondCall, expected, 'second call should still be cached value') 328 | 329 | done() 330 | }) 331 | 332 | test('first', 1, function () { 333 | ok(v.first(['a', 'b', 'c']) == 'a', 'a is first'); 334 | }) 335 | 336 | test('last', 1, function () { 337 | ok(v.last(['a', 'b', 'c']) == 'c', 'c is last'); 338 | }) 339 | 340 | test('keys', 1, function () { 341 | var actual = v.keys({ 342 | a: 'foo', 343 | b: 'bar', 344 | c: 'baz' 345 | }); 346 | var expected = ['a', 'b', 'c'] 347 | ok(v.every(actual, function (el, i) { 348 | return el == expected[i] 349 | }), "a, b, c were keys") 350 | }) 351 | 352 | test('values', 1, function () { 353 | var actual = v.values({ 354 | a: 'foo' 355 | , b: 'bar' 356 | , c: 'baz' 357 | }) 358 | var expected = ['foo', 'bar', 'baz'] 359 | ok(v.every(actual, function (el, i) { 360 | return el == expected[i] 361 | }), "foo, bar, baz values were found") 362 | }) 363 | 364 | test('trim', 1, function () { 365 | ok(v.trim(' \n\r omg bbq wtf \n\n ') === 'omg bbq wtf', 'string was trimmed') 366 | }) 367 | 368 | // bind() and curry() are the same except bind() takes a scope argument at the begining 369 | function testBindAndCurry(type) { 370 | var expected, o = { foo: 'bar' }, ret = { bar: 'foo' } 371 | 372 | // our function to curry 373 | function itburns() { 374 | type === 'bind' && ok(this === o && this.foo === 'bar', 'bound to correct object') 375 | ok(arguments.length === expected.length, expected.length + ' arguments supplied from curried function') 376 | var isok = true 377 | for (var i = 0; i < expected.length; i++) { 378 | if (expected[i] !== arguments[i]) 379 | isok = false 380 | } 381 | ok(isok, 'arguments identical to expected') 382 | return ret 383 | } 384 | 385 | // test executor, first arg is what we pass to curry()/bind() as the curry arguments 386 | // second arg is what we call the curried/bound function with, both of these arguments 387 | // together should be what we get in 'expected' 388 | function runtest(curriedargs, calledargs) { 389 | var vargs = (type === 'bind' ? [ o, itburns ] : [ itburns ]).concat(curriedargs) // arguments to pass to v.bind()/v.curry() 390 | , fn = v[type].apply(null, vargs) 391 | 392 | var r = fn.apply(null, calledargs) 393 | ok(r === ret, 'returned correct object') 394 | } 395 | 396 | expected = [] 397 | runtest([], []) 398 | 399 | expected = [ 'additional' ] 400 | runtest([], [ 'additional' ]) 401 | 402 | expected = ['one', 'two', [ 'three', 'three' ]] 403 | runtest(expected, []) 404 | 405 | expected = [ 'one', 'two', [ 'three', 'three' ], 'additional', [ 'yee', 'haw' ]] 406 | runtest([ 'one', 'two', expected[2] ], [ 'additional', expected[4] ]) 407 | } 408 | 409 | test('bind', 16, function () { 410 | testBindAndCurry('bind') 411 | }) 412 | 413 | test('curry', 12, function () { 414 | testBindAndCurry('curry') 415 | }) 416 | 417 | test('parallel', 3, function () { 418 | function getTimeline(fn) { 419 | setTimeout(function() { 420 | fn(null, 'one', 'two') 421 | }, 50) 422 | } 423 | function getUser(fn) { 424 | setTimeout(function() { 425 | fn(null, 'three') 426 | }, 25) 427 | } 428 | v.parallel( 429 | function (fn) { 430 | getTimeline(function (e, one, two) { 431 | fn(e, one, two) 432 | }) 433 | } 434 | , function (fn) { 435 | getUser(function (e, three) { 436 | fn(e, three) 437 | }) 438 | } 439 | , function (e, one, two, three) { 440 | if (e) return console.log(e) 441 | assert(one, 'one', 'first result is "one"') 442 | assert(two, 'two', 'second result is "two"') 443 | assert(three, 'three', 'third result is "three"') 444 | } 445 | ) 446 | }) 447 | test('parallel with defined signature interface', 3, function () { 448 | function getTimeline(fn) { 449 | setTimeout(function() { 450 | fn(null, 'one', 'two') 451 | }, 50) 452 | } 453 | function getUser(fn) { 454 | setTimeout(function() { 455 | fn(null, 'three') 456 | }, 25) 457 | } 458 | v.parallel([ 459 | function (fn) { 460 | getTimeline(function (e, one, two) { 461 | fn(e, one, two) 462 | }) 463 | } 464 | , function (fn) { 465 | getUser(function (e, three) { 466 | fn(e, three) 467 | }) 468 | }] 469 | 470 | , function (e, one, two, three) { 471 | if (e) return console.log(e) 472 | assert(one, 'one', 'first result is "one"') 473 | assert(two, 'two', 'second result is "two"') 474 | assert(three, 'three', 'third result is "three"') 475 | } 476 | ) 477 | }) 478 | 479 | test('parallel flattten', 6, function () { 480 | v.parallel([ 481 | function (fn) { 482 | fn(null, ['first one', 'second one']) 483 | } 484 | , function (fn) { 485 | fn(null, ['first two', 'second two', 'third two'], ['first three', 'second three', 'third three', 'fourth three']) 486 | } 487 | ] 488 | , function (er, first, second, third) { 489 | assert(first.length, 2, 'first arg has two items') 490 | assert(second.length, 3, 'second arg has three items') 491 | assert(third.length, 4, 'third arg has four items') 492 | 493 | // sanity checks 494 | assert(first[0], 'first one', 'first arg is array') 495 | assert(second[2], 'third two', 'second arg is array with three items') 496 | assert(third[3], 'fourth three', 'third arg has four items') 497 | }) 498 | }) 499 | 500 | test('parallel should throw an exception with an empty array', 1, function () { 501 | var timer = setTimeout(function () { 502 | ok(false, 'Failed by not throwing an exception from an empty array') 503 | }, 50) 504 | try { 505 | v.parallel([], function (err) { 506 | 507 | }) 508 | } catch (ex) { 509 | clearTimeout(timer) 510 | ok(true, 'exception thrown with Empty array') 511 | } 512 | }) 513 | 514 | test('waterfall', 7, function () { 515 | var index = 0 516 | , results = ['first', 'second', 'third'] 517 | v.waterfall([ 518 | function (callback) { 519 | setTimeout(function() { 520 | ok(results[index++] == 'first', 'first waterfall method is fired') 521 | callback(null, 'obvious', 'corp') 522 | }, 150) 523 | } 524 | , function (a, b, callback) { 525 | setTimeout(function() { 526 | ok(results[index++] == 'second', 'second waterfall method is fired') 527 | ok('obvious' == a, 'a == "obvious"') 528 | ok('corp' == b, 'b == "corp"') 529 | callback(null, 'final result') 530 | }, 50) 531 | }] 532 | , function (err, rez) { 533 | ok(results[index++] == 'third', 'final callback is fired with result "third"') 534 | ok(rez == 'final result', 'rez is "final results"') 535 | ok(err == null, 'there is no error') 536 | }) 537 | }) 538 | 539 | test('waterfall with unlimitted args', 7, function () { 540 | var index = 0 541 | , results = ['first', 'second', 'third'] 542 | v.waterfall( 543 | function (callback) { 544 | setTimeout(function() { 545 | ok(results[index++] == 'first', 'first waterfall method is fired') 546 | callback(null, 'obvious', 'corp') 547 | }, 150) 548 | } 549 | , function (a, b, callback) { 550 | setTimeout(function() { 551 | ok(results[index++] == 'second', 'second waterfall method is fired') 552 | ok('obvious' == a, 'a == "obvious"') 553 | ok('corp' == b, 'b == "corp"') 554 | callback(null, 'final result') 555 | }, 50) 556 | } 557 | , function (err, rez) { 558 | ok(results[index++] == 'third', 'final callback is fired with result "third"') 559 | ok(rez == 'final result', 'rez is "final results"') 560 | ok(err == null, 'there is no error') 561 | }) 562 | }) 563 | 564 | test('waterfall with an empty array should throw exception', 1, function () { 565 | var timer = setTimeout(function () { 566 | ok(false, 'Failed by not throwing an exception from an empty array') 567 | }, 50) 568 | try { 569 | v.waterfall([], function (err) { 570 | 571 | }) 572 | } catch (ex) { 573 | clearTimeout(timer) 574 | ok(true, 'exception thrown with Empty array') 575 | } 576 | }) 577 | 578 | test('series', function (done) { 579 | var index = 0 580 | , results = ['first', 'second', 'third'] 581 | v.series([ 582 | function (callback) { 583 | setTimeout(function() { 584 | ok(results[index++] == 'first', 'first waterfall method is fired') 585 | callback(null, 'obvious', 'corp') 586 | }, 150) 587 | } 588 | , function (callback) { 589 | setTimeout(function() { 590 | ok(results[index++] == 'second', 'second waterfall method is fired') 591 | callback(null, 'final result') 592 | }, 50) 593 | }] 594 | , function (err) { 595 | ok(results[index++] == 'third', 'final callback is fired with result "third"') 596 | ok(err == null, 'there is no error') 597 | done() 598 | }) 599 | }) 600 | 601 | test('series with an error', function (done) { 602 | var index = 0 603 | , results = ['first', 'second', 'third'] 604 | v.series([ 605 | function (callback) { 606 | setTimeout(function() { 607 | ok(results[index++] == 'first', 'first waterfall method is fired') 608 | callback('Error', 'obvious', 'corp') 609 | }, 150) 610 | } 611 | , function (callback) { 612 | setTimeout(function() { 613 | ok(false, 'should not be called') 614 | callback(null, 'final result') 615 | }, 50) 616 | }] 617 | , function (err) { 618 | ok(err == 'Error', 'first arg returns error') 619 | done() 620 | }) 621 | }) 622 | 623 | test('function queue', 3, function () { 624 | var results = ['first', 'second', 'third'] 625 | , index = 0 626 | 627 | var q = v.queue( 628 | function () { 629 | ok(results[index++] == 'first', 'first queue method is fired') 630 | setTimeout(function() { 631 | q.next() 632 | }, 0) 633 | } 634 | , function () { 635 | ok(results[index++] == 'second', 'second queue method is fired') 636 | q.next() 637 | } 638 | , function () { 639 | ok(results[index++] == 'third', 'third queue method is fired') 640 | q.next() 641 | }).next() 642 | }) 643 | 644 | test('function queue as an array', 3, function () { 645 | var results = ['first', 'second', 'third'] 646 | , index = 0 647 | 648 | var q = v.queue([ 649 | function() { 650 | ok(results[index++] == 'first', 'first queue method is fired') 651 | q.next() 652 | } 653 | , function () { 654 | ok(results[index++] == 'second', 'second queue method is fired') 655 | q.next() 656 | } 657 | , function () { 658 | ok(results[index++] == 'third', 'third queue method is fired') 659 | q.next() 660 | }]) 661 | q.next() 662 | }) 663 | 664 | test('throttle', function (done) { 665 | var called = 0 666 | var start = new Date() 667 | var throttler = v.throttle(50, function () { 668 | ++called 669 | var now = new Date() 670 | var diff = now - start 671 | if (called == 1) { 672 | ok(diff >= 50, 'first throttle is past 50ms') 673 | } 674 | if (called == 2) { 675 | ok(diff >= 100, '2nd call is throttled at 100ms') 676 | done() 677 | } 678 | }) 679 | throttler() 680 | throttler() 681 | setTimeout(throttler, 50) 682 | }) 683 | 684 | test('debounce', function (done) { 685 | var flag = true 686 | var debouncer = v.debounce(100, function () { 687 | flag = false 688 | ok(true, 'debouncer called') 689 | var now = new Date() 690 | ok(now - start >= 100, 'debounce time has passed') 691 | done() 692 | }) 693 | debouncer() 694 | ok(flag, 'debouncer not called') 695 | debouncer() 696 | ok(flag, 'debouncer not called') 697 | debouncer() 698 | ok(flag, 'debouncer not called') 699 | var start = new Date() 700 | }) 701 | 702 | test('throttleDebounce', function (done) { 703 | var called = 0 704 | var interval 705 | var fn = v.throttleDebounce(100, 50, function () { 706 | var now = new Date() 707 | ++called 708 | if (called == 1) { 709 | ok(now - start >= 50 && now - start < 100, 'debounce called before throttle first pass') 710 | interval = setInterval(fn, 10) 711 | } 712 | if (called == 2) { 713 | ok(now - start >= 150, 'throttle called after several debounces') 714 | clearInterval(interval) 715 | done() 716 | } 717 | }) 718 | var start = new Date() 719 | fn() 720 | 721 | }) 722 | 723 | }) 724 | 725 | sink('Type Checking', function (test, ok) { 726 | 727 | test('String', 3, function () { 728 | ok(v.is.string('hello'), 'v.is.str("hello")') 729 | ok(v.is.string(''), 'v.is.str("")') 730 | ok(!v.is.string(null), '!v.is.str(null)') 731 | }) 732 | 733 | test('Function', 6, function () { 734 | ok(v.is.func(function () {}), 'function () {}') 735 | ok(v.is.func(Function), 'Function') 736 | ok(v.is.func(new Function), 'new Function') 737 | ok(!v.is.func({}), 'not {}') 738 | ok(!v.is.func([]), 'not []') 739 | ok(!v.is.func(''), 'not ""') 740 | }) 741 | 742 | test('Array', 4, function () { 743 | ok(v.is.array([]), '[]') 744 | ok(v.is.array(Array(1)), 'Array(1)') 745 | ok(v.is.array(new Array), 'new Array') 746 | ok(!v.is.array(Object), 'not Object') 747 | }) 748 | 749 | test('Number', 3, function () { 750 | ok(v.is.num(1), '1') 751 | ok(v.is.num(1.1), '1.1') 752 | ok(!v.is.num('1'), '"1"') 753 | }) 754 | 755 | test('Boolean', 6, function () { 756 | ok(v.is.bool(false), 'false') 757 | ok(v.is.bool(true), 'true') 758 | ok(v.is.bool(!0), '!0') 759 | ok(v.is.bool(!!1), '!!1') 760 | ok(!v.is.bool('true'), '"true"') 761 | ok(!v.is.bool('false'), '"false"') 762 | }) 763 | 764 | test('Arguments', 1, function () { 765 | (function () { 766 | ok(v.is.args(arguments), 'arguments') 767 | })() 768 | }) 769 | 770 | test('Empty', 6, function () { 771 | ok(v.is.empty({}), '{}') 772 | ok(v.is.empty([]), '[]') 773 | ok(v.is.empty(''), '""') 774 | ok(!v.is.empty({foo:'bar'}), '{foo:bar}') 775 | ok(!v.is.empty([1]), '[1]') 776 | ok(!v.is.empty('i'), '"i"') 777 | }) 778 | 779 | test('Date', 1, function () { 780 | ok(v.is.date(new Date), 'new Date') 781 | }) 782 | 783 | test('RegExp', 2, function () { 784 | ok(v.is.regexp(/i/), '/i/') 785 | ok(v.is.regexp(new RegExp("i")), 'new RegExp("i")') 786 | }) 787 | 788 | test('Null', 3, function () { 789 | ok(v.is.nil(null), 'null') 790 | ok(!v.is.nil(""), '""') 791 | ok(!v.is.nil(), 'undefined') 792 | }) 793 | 794 | test('Undefined', 3, function () { 795 | ok(v.is.undef(), 'no args') 796 | ok(v.is.undef(undefined), 'undefined') 797 | ok(!v.is.undef(null), 'undefined') 798 | }) 799 | 800 | test('Object', 4, function () { 801 | ok(v.is.obj({}), '{}') 802 | ok(v.is.obj(new Object), 'Object') 803 | ok(!v.is.obj([]), 'not []') 804 | ok(!v.is.obj(function() {}), 'not function(){}') 805 | }) 806 | 807 | if (typeof window !== 'undefined' && window.document) { 808 | test('Element', 5, function () { 809 | ok(v.is.element(document.body), 'document.body') 810 | ok(v.is.element(document.createElement('div')), 'createElement("div")') 811 | ok(!v.is.element({}), 'not {}') 812 | ok(!v.is.element([]), 'not []') 813 | ok(!v.is.element(document.getElementsByTagName('body')), 'not getElementsByTagName()') 814 | }) 815 | } 816 | 817 | }) 818 | 819 | sink('OO Style and chaining', function (test, ok) { 820 | 821 | test('method chains', 1, function () { 822 | var expected = ['A', 'B', 'C']; 823 | var actual = v(['a', 'b', [['c']], 0, false,,,null,['a', 'b', 'c']]) 824 | .chain().flatten().compact().uniq().map(function (letter) { 825 | return letter.toUpperCase(); 826 | }).value(); 827 | ok(v.every(actual, function (el, i) { 828 | return el == expected[i]; 829 | }), "all methods chained together"); 830 | }); 831 | 832 | }); 833 | 834 | sink('Funny business', function (test, ok) { 835 | test('can call each on nodeList', 1, function () { 836 | if (typeof document !== 'undefined') { 837 | // this is a browser test 838 | v.each(document.getElementsByTagName('body'), function (el) { 839 | ok(el.tagName.match(/body/i), 'element was found by each() iteration'); 840 | }) 841 | } else { 842 | ok(true, 'noop test') 843 | } 844 | }) 845 | }) 846 | 847 | start() 848 | -------------------------------------------------------------------------------- /valentine.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Valentine: JavaScript's functional Sister 3 | * (c) Dustin Diaz 2014 4 | * https://github.com/ded/valentine License MIT 5 | */ 6 | 7 | (function (name, context, definition) { 8 | if (typeof module != 'undefined') module.exports = definition() 9 | else if (typeof define == 'function') define(definition) 10 | else context[name] = context['v'] = definition() 11 | })('valentine', this, function () { 12 | 13 | var ap = [] 14 | , hasOwn = Object.prototype.hasOwnProperty 15 | , n = null 16 | , slice = ap.slice 17 | 18 | var iters = { 19 | each: function (a, fn, scope) { 20 | ap.forEach.call(a, fn, scope) 21 | } 22 | 23 | , map: function (a, fn, scope) { 24 | return ap.map.call(a, fn, scope) 25 | } 26 | 27 | , some: function (a, fn, scope) { 28 | return a.some(fn, scope) 29 | } 30 | 31 | , every: function (a, fn, scope) { 32 | return a.every(fn, scope) 33 | } 34 | 35 | , filter: function (a, fn, scope) { 36 | return a.filter(fn, scope) 37 | } 38 | , indexOf: function (a, el, start) { 39 | return a.indexOf(el, isFinite(start) ? start : 0) 40 | } 41 | 42 | , lastIndexOf: function (a, el, start) { 43 | return a.lastIndexOf(el, isFinite(start) ? start : a.length) 44 | } 45 | 46 | , reduce: function (o, i, m, c) { 47 | return ap.reduce.call(o, v.bind(c, i), m); 48 | } 49 | 50 | , reduceRight: function (o, i, m, c) { 51 | return ap.reduceRight.call(o, v.bind(c, i), m) 52 | } 53 | 54 | , find: function (obj, iterator, context) { 55 | var result 56 | iters.some(obj, function (value, index, list) { 57 | if (iterator.call(context, value, index, list)) { 58 | result = value 59 | return true 60 | } 61 | }) 62 | return result 63 | } 64 | 65 | , reject: function (a, fn, scope) { 66 | var r = [] 67 | for (var i = 0, j = 0, l = a.length; i < l; i++) { 68 | if (i in a) { 69 | if (fn.call(scope, a[i], i, a)) { 70 | continue; 71 | } 72 | r[j++] = a[i] 73 | } 74 | } 75 | return r 76 | } 77 | 78 | , size: function (a) { 79 | return o.toArray(a).length 80 | } 81 | 82 | , compact: function (a) { 83 | return iters.filter(a, function (value) { 84 | return !!value 85 | }) 86 | } 87 | 88 | , flatten: function (a) { 89 | return iters.reduce(a, function (memo, value) { 90 | if (is.arr(value)) { 91 | return memo.concat(iters.flatten(value)) 92 | } 93 | memo[memo.length] = value 94 | return memo 95 | }, []) 96 | } 97 | 98 | , uniq: function (ar, opt_iterator) { 99 | if (ar === null) return [] 100 | var a = [], seen = [] 101 | for (var i = 0, length = ar.length; i < length; i++) { 102 | var value = ar[i] 103 | if (opt_iterator) value = opt_iterator(value, i, ar) 104 | if (!iters.inArray(seen, value)) { 105 | seen.push(value) 106 | a.push(ar[i]) 107 | } 108 | } 109 | return a 110 | } 111 | 112 | , merge: function (one, two) { 113 | var i = one.length, j = 0, l 114 | if (isFinite(two.length)) { 115 | for (l = two.length; j < l; j++) { 116 | one[i++] = two[j] 117 | } 118 | } else { 119 | while (two[j] !== undefined) { 120 | one[i++] = two[j++] 121 | } 122 | } 123 | one.length = i 124 | return one 125 | } 126 | 127 | , inArray: function (ar, needle) { 128 | return !!~iters.indexOf(ar, needle) 129 | } 130 | 131 | , memo: function (fn, hasher) { 132 | var store = {} 133 | hasher || (hasher = function (v) { 134 | return v 135 | }) 136 | return function () { 137 | var key = hasher.apply(this, arguments) 138 | return hasOwn.call(store, key) ? store[key] : (store[key] = fn.apply(this, arguments)) 139 | } 140 | } 141 | } 142 | 143 | var is = { 144 | fun: function (f) { 145 | return typeof f === 'function' 146 | } 147 | 148 | , str: function (s) { 149 | return typeof s === 'string' 150 | } 151 | 152 | , ele: function (el) { 153 | return !!(el && el.nodeType && el.nodeType == 1) 154 | } 155 | 156 | , arr: function (ar) { 157 | return ar instanceof Array 158 | } 159 | 160 | , arrLike: function (ar) { 161 | return (ar && ar.length && isFinite(ar.length)) 162 | } 163 | 164 | , num: function (n) { 165 | return typeof n === 'number' 166 | } 167 | 168 | , bool: function (b) { 169 | return (b === true) || (b === false) 170 | } 171 | 172 | , args: function (a) { 173 | return !!(a && hasOwn.call(a, 'callee')) 174 | } 175 | 176 | , emp: function (o) { 177 | var i = 0 178 | return is.arr(o) ? o.length === 0 : 179 | is.obj(o) ? (function () { 180 | for (var _ in o) { 181 | i++ 182 | break; 183 | } 184 | return (i === 0) 185 | }()) : 186 | o === '' 187 | } 188 | 189 | , dat: function (d) { 190 | return !!(d && d.getTimezoneOffset && d.setUTCFullYear) 191 | } 192 | 193 | , reg: function (r) { 194 | return !!(r && r.test && r.exec && (r.ignoreCase || r.ignoreCase === false)) 195 | } 196 | 197 | , nan: function (n) { 198 | return n !== n 199 | } 200 | 201 | , nil: function (o) { 202 | return o === n 203 | } 204 | 205 | , und: function (o) { 206 | return typeof o === 'undefined' 207 | } 208 | 209 | , def: function (o) { 210 | return typeof o !== 'undefined' 211 | } 212 | 213 | , obj: function (o) { 214 | return o instanceof Object && !is.fun(o) && !is.arr(o) 215 | } 216 | } 217 | 218 | // nicer looking aliases 219 | is.empty = is.emp 220 | is.date = is.dat 221 | is.regexp = is.reg 222 | is.element = is.ele 223 | is.array = is.arr 224 | is.string = is.str 225 | is.undef = is.und 226 | is.func = is.fun 227 | 228 | var o = { 229 | each: function each(a, fn, scope) { 230 | is.arrLike(a) ? 231 | iters.each(a, fn, scope) : (function () { 232 | for (var k in a) { 233 | hasOwn.call(a, k) && fn.call(scope, k, a[k], a) 234 | } 235 | }()) 236 | } 237 | 238 | , map: function map(a, fn, scope) { 239 | var r = [], i = 0 240 | return is.arrLike(a) ? 241 | iters.map(a, fn, scope) : !function () { 242 | for (var k in a) { 243 | hasOwn.call(a, k) && (r[i++] = fn.call(scope, k, a[k], a)) 244 | } 245 | }() && r 246 | } 247 | 248 | , some: function some(a, fn, scope) { 249 | if (is.arrLike(a)) return iters.some(a, fn, scope) 250 | for (var k in a) { 251 | if (hasOwn.call(a, k) && fn.call(scope, k, a[k], a)) { 252 | return true 253 | } 254 | } 255 | return false 256 | 257 | } 258 | 259 | , every: function every(a, fn, scope) { 260 | if (is.arrLike(a)) return iters.every(a, fn, scope) 261 | for (var k in a) { 262 | if (!(hasOwn.call(a, k) && fn.call(scope, k, a[k], a))) { 263 | return false 264 | } 265 | } 266 | return true 267 | } 268 | 269 | , filter: function filter(a, fn, scope) { 270 | var r = {}, k 271 | if (is.arrLike(a)) return iters.filter(a, fn, scope) 272 | for (k in a) { 273 | if (hasOwn.call(a, k) && fn.call(scope, k, a[k], a)) { 274 | r[k] = a[k] 275 | } 276 | } 277 | return r 278 | } 279 | 280 | , pluck: function pluck(a, k) { 281 | return is.arrLike(a) ? 282 | iters.map(a, function (el) { 283 | return el[k] 284 | }) : 285 | o.map(a, function (_, v) { 286 | return v[k] 287 | }) 288 | } 289 | 290 | , toArray: function toArray(a) { 291 | if (!a) return [] 292 | 293 | if (is.arr(a)) return a 294 | 295 | if (a.toArray) return a.toArray() 296 | 297 | if (is.args(a)) return slice.call(a) 298 | 299 | return iters.map(a, function (k) { 300 | return k 301 | }) 302 | } 303 | 304 | , first: function first(a) { 305 | return a[0] 306 | } 307 | 308 | , last: function last(a) { 309 | return a[a.length - 1] 310 | } 311 | 312 | , keys: Object.keys 313 | , values: function (ob) { 314 | return o.map(ob, function (_, v) { 315 | return v 316 | }) 317 | } 318 | 319 | , extend: function extend() { 320 | // based on jQuery deep merge 321 | var options, name, src, copy, clone 322 | , target = arguments[0], i = 1, length = arguments.length 323 | 324 | for (; i < length; i++) { 325 | if ((options = arguments[i]) !== n) { 326 | // Extend the base object 327 | for (name in options) { 328 | src = target[name] 329 | copy = options[name] 330 | if (target === copy) { 331 | continue; 332 | } 333 | if (copy && (is.obj(copy))) { 334 | clone = src && is.obj(src) ? src : {} 335 | target[name] = o.extend(clone, copy); 336 | } else if (copy !== undefined) { 337 | target[name] = copy 338 | } 339 | } 340 | } 341 | } 342 | return target 343 | } 344 | 345 | , trim: function (s) { 346 | return s.trim() 347 | } 348 | 349 | , bind: function (scope, fn) { 350 | var args = arguments.length > 2 ? slice.call(arguments, 2) : null 351 | return function () { 352 | return fn.apply(scope, args ? args.concat(slice.call(arguments)) : arguments) 353 | } 354 | } 355 | 356 | , curry: function curry(fn) { 357 | if (arguments.length == 1) return fn 358 | var args = slice.call(arguments, 1) 359 | return function () { 360 | return fn.apply(null, args.concat(slice.call(arguments))) 361 | } 362 | } 363 | 364 | , parallel: function parallel(fns, callback) { 365 | var args = o.toArray(arguments) 366 | , len = 0 367 | , returns = [] 368 | , flattened = [] 369 | 370 | if (is.arr(fns) && fns.length === 0 || (is.fun(fns) && args.length === 1)) throw new TypeError('Empty parallel array') 371 | if (!is.arr(fns)) { 372 | callback = args.pop() 373 | fns = args 374 | } 375 | 376 | iters.each(fns, function (el, i) { 377 | el(function () { 378 | var a = o.toArray(arguments) 379 | , e = a.shift() 380 | if (e) return callback(e) 381 | returns[i] = a 382 | if (fns.length == ++len) { 383 | returns.unshift(n) 384 | 385 | iters.each(returns, function (r) { 386 | flattened = flattened.concat(r) 387 | }) 388 | 389 | callback.apply(n, flattened) 390 | } 391 | }) 392 | }) 393 | } 394 | 395 | , waterfall: function waterfall(fns, callback) { 396 | var args = o.toArray(arguments) 397 | 398 | if (is.arr(fns) && fns.length === 0 || (is.fun(fns) && args.length === 1)) throw new TypeError('Empty waterfall array') 399 | if (!is.arr(fns)) { 400 | callback = args.pop() 401 | fns = args 402 | } 403 | 404 | (function f() { 405 | var args = o.toArray(arguments) 406 | if (!args.length) args.push(null) // allow callbacks with no args as passable non-errored functions 407 | args.push(f) 408 | var err = args.shift() 409 | if (!err && fns.length) fns.shift().apply(n, args) 410 | else { 411 | args.pop() 412 | args.unshift(err) 413 | callback.apply(n, args) 414 | } 415 | }(n)) 416 | } 417 | 418 | , series: function (tasks, callback) { 419 | o.waterfall(tasks.map(function (task) { 420 | return function (f) { 421 | task(function (err) { 422 | f(err) 423 | }) 424 | } 425 | }), callback) 426 | } 427 | 428 | , queue: function queue(ar) { 429 | return new Queue(is.arrLike(ar) ? ar : o.toArray(arguments)) 430 | } 431 | 432 | , debounce: function debounce(wait, fn, opt_scope) { 433 | var timeout 434 | function caller() { 435 | var args = arguments 436 | , context = opt_scope || this 437 | function later() { 438 | timeout = null 439 | fn.apply(context, args) 440 | } 441 | clearTimeout(timeout) 442 | timeout = setTimeout(later, wait) 443 | } 444 | 445 | // cancelation method 446 | caller.cancel = function debounceCancel() { 447 | clearTimeout(timeout) 448 | timeout = null 449 | } 450 | 451 | return caller 452 | } 453 | 454 | , throttle: function throttle(wait, fn, opt_scope, head) { 455 | var timeout 456 | var origHead = head 457 | return function throttler() { 458 | var context = opt_scope || this 459 | , args = arguments 460 | if (head) { 461 | fn.apply(context, args) 462 | head = false 463 | return 464 | } 465 | if (!timeout) { 466 | timeout = setTimeout(function throttleTimeout() { 467 | fn.apply(context, args) 468 | timeout = null 469 | head = origHead 470 | }, 471 | wait 472 | ) 473 | } 474 | } 475 | } 476 | 477 | , throttleDebounce: function (throttleMs, debounceMs, fn, opt_scope) { 478 | var args 479 | , context 480 | , debouncer 481 | , throttler 482 | 483 | function caller() { 484 | args = arguments 485 | context = opt_scope || this 486 | 487 | clearTimeout(debouncer) 488 | debouncer = setTimeout(function () { 489 | clearTimeout(throttler) 490 | throttler = null 491 | fn.apply(context, args) 492 | }, debounceMs) 493 | 494 | if (!throttler) { 495 | throttler = setTimeout(function () { 496 | clearTimeout(debouncer) 497 | throttler = null 498 | fn.apply(context, args) 499 | }, throttleMs) 500 | } 501 | } 502 | 503 | // cancelation method 504 | caller.cancel = function () { 505 | clearTimeout(debouncer) 506 | clearTimeout(throttler) 507 | throttler = null 508 | } 509 | 510 | return caller 511 | } 512 | } 513 | 514 | function Queue (a) { 515 | this.values = a 516 | this.index = 0 517 | } 518 | 519 | Queue.prototype.next = function () { 520 | this.index < this.values.length && this.values[this.index++]() 521 | return this 522 | } 523 | 524 | function v(a, scope) { 525 | return new Valentine(a, scope) 526 | } 527 | 528 | function aug(o, o2) { 529 | for (var k in o2) o[k] = o2[k] 530 | } 531 | 532 | aug(v, iters) 533 | aug(v, o) 534 | v.is = is 535 | 536 | v.v = v // vainglory 537 | 538 | // peoples like the object style 539 | function Valentine(a, scope) { 540 | this.val = a 541 | this._scope = scope || n 542 | this._chained = 0 543 | } 544 | 545 | v.each(v.extend({}, iters, o), function (name, fn) { 546 | Valentine.prototype[name] = function () { 547 | var a = v.toArray(arguments) 548 | a.unshift(this.val) 549 | var ret = fn.apply(this._scope, a) 550 | this.val = ret 551 | return this._chained ? this : ret 552 | } 553 | }) 554 | 555 | // people like chaining 556 | aug(Valentine.prototype, { 557 | chain: function () { 558 | this._chained = 1 559 | return this 560 | } 561 | , value: function () { 562 | return this.val 563 | } 564 | }) 565 | 566 | return v 567 | }); 568 | -------------------------------------------------------------------------------- /valentine.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Valentine: JavaScript's functional Sister 3 | * (c) Dustin Diaz 2014 4 | * https://github.com/ded/valentine License MIT 5 | */ 6 | (function(e,t,n){typeof module!="undefined"?module.exports=n():typeof define=="function"?define(n):t[e]=t.v=n()})("valentine",this,function(){function u(e){this.values=e,this.index=0}function a(e,t){return new l(e,t)}function f(e,t){for(var n in t)e[n]=t[n]}function l(e,t){this.val=e,this._scope=t||n,this._chained=0}var e=[],t=Object.prototype.hasOwnProperty,n=null,r=e.slice,i={each:function(t,n,r){e.forEach.call(t,n,r)},map:function(t,n,r){return e.map.call(t,n,r)},some:function(e,t,n){return e.some(t,n)},every:function(e,t,n){return e.every(t,n)},filter:function(e,t,n){return e.filter(t,n)},indexOf:function(e,t,n){return e.indexOf(t,isFinite(n)?n:0)},lastIndexOf:function(e,t,n){return e.lastIndexOf(t,isFinite(n)?n:e.length)},reduce:function(t,n,r,i){return e.reduce.call(t,a.bind(i,n),r)},reduceRight:function(t,n,r,i){return e.reduceRight.call(t,a.bind(i,n),r)},find:function(e,t,n){var r;return i.some(e,function(e,i,s){if(t.call(n,e,i,s))return r=e,!0}),r},reject:function(e,t,n){var r=[];for(var i=0,s=0,o=e.length;i2?r.call(arguments,2):null;return function(){return t.apply(e,n?n.concat(r.call(arguments)):arguments)}},curry:function(t){if(arguments.length==1)return t;var n=r.call(arguments,1);return function(){return t.apply(null,n.concat(r.call(arguments)))}},parallel:function(t,r){var u=o.toArray(arguments),a=0,f=[],l=[];if(s.arr(t)&&t.length===0||s.fun(t)&&u.length===1)throw new TypeError("Empty parallel array");s.arr(t)||(r=u.pop(),t=u),i.each(t,function(e,s){e(function(){var e=o.toArray(arguments),u=e.shift();if(u)return r(u);f[s]=e,t.length==++a&&(f.unshift(n),i.each(f,function(e){l=l.concat(e)}),r.apply(n,l))})})},waterfall:function(t,r){var i=o.toArray(arguments);if(s.arr(t)&&t.length===0||s.fun(t)&&i.length===1)throw new TypeError("Empty waterfall array");s.arr(t)||(r=i.pop(),t=i),function u(){var e=o.toArray(arguments);e.length||e.push(null),e.push(u);var i=e.shift();!i&&t.length?t.shift().apply(n,e):(e.pop(),e.unshift(i),r.apply(n,e))}(n)},series:function(e,t){o.waterfall(e.map(function(e){return function(t){e(function(e){t(e)})}}),t)},queue:function(t){return new u(s.arrLike(t)?t:o.toArray(arguments))},debounce:function(t,n,r){function s(){function o(){i=null,n.apply(s,e)}var e=arguments,s=r||this;clearTimeout(i),i=setTimeout(o,t)}var i;return s.cancel=function(){clearTimeout(i),i=null},s},throttle:function(t,n,r,i){var s,o=i;return function(){var u=r||this,a=arguments;if(i){n.apply(u,a),i=!1;return}s||(s=setTimeout(function(){n.apply(u,a),s=null,i=o},t))}},throttleDebounce:function(e,t,n,r){function a(){i=arguments,s=r||this,clearTimeout(o),o=setTimeout(function(){clearTimeout(u),u=null,n.apply(s,i)},t),u||(u=setTimeout(function(){clearTimeout(o),u=null,n.apply(s,i)},e))}var i,s,o,u;return a.cancel=function(){clearTimeout(o),clearTimeout(u),u=null},a}};return u.prototype.next=function(){return this.index