├── .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 | [](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