UNPKG

useless

Version:

Use Less. Do More. JavaScript on steroids.

441 lines (327 loc) 16.2 kB
"use strict"; const _ = require ('underscore') /* CPS primitives module ======================================================================== */ _.cps = function () { return _.cps.sequence.apply (null, arguments) } /* apply ======================================================================== */ _.withTest (['cps', 'apply'], function () { // TODO }, function () { _.cps.apply = function (fn, this_, args_, then) { var args = _.asArray (args_) var lastArgN = _.numArgs (fn) - 1 var thenArg = args[lastArgN] args[lastArgN] = function () { then.call (this, arguments, thenArg) } return fn.apply (this_, args) } }) /* each ======================================================================== */ _.withTest (['cps', 'each'], function () { /* Example array */ var data = ['foo', 'bar', 'baz'] var currentIndex = 0 _.cps.each (data, /* called for each item, in linear order */ function (item, itemIndex, then, complete, arrayWeTraverse) { $assert (item === data[itemIndex]) $assert (itemIndex === currentIndex++) $assert (arrayWeTraverse === data) $assert (_.isFunction (then)) $assert (_.isFunction (complete)) then () }, /* called when all items enumerated */ function () { $assert (currentIndex === data.length) }) /* You can omit 'index' argument for iterator function */ var data2 = [] _.cps.each (data, function (item, then) { data2.push (item); then () }, function () { $assert (data, data2) }) /* You can stop iteration by calling fourth argument */ var data3 = [] _.cps.each (data, function (item, i, then, break_) { data3.push (item); break_ () }, function () { $assert (data3, ['foo']) }) /* Iterating over dictionary is legal */ $assertEveryCalled (function (items__3, final__1) { var data2 = { 'foo': 1, 'bar': 2, 'baz': 3 } _.cps.each ( data2, function (item, name, then) { $assert (item === data2[name]); items__3 (); then () }, function () { final__1 () }) }) /* Iterating over scalar is legal */ $assertEveryCalled (function (items__1, final__1) { _.cps.each ( 'foo', function (item, name, then) { $assert ([item, name], ['foo', undefined]); items__1 (); then () }, function () { final__1 () }) }) /* Undefined/null are treated as empty (not scalars) */ $assertEveryCalled (function (final__1) { _.cps.each ( undefined, function () { $fail }, function () { final__1 () }) }) }, function () { _.extend (_.cps, { each: function each (obj, elem_, complete_ = _.noop, index_, length_, keys_) { let completed = false var complete = (...args) => !completed && (completed = true, complete_ (...args)) var elem = function (x, k, next) { if (_.numArgs (elem_) === 2) { elem_ (x, next, complete, obj) } else { elem_ (x, k, next, complete, obj) } } if (_.isEmpty (obj)) { complete () } else if (_.isScalar (obj)) { elem (obj, undefined, complete) } else { var index = index_ || 0 var keys = index === 0 ? (obj.length === undefined ? _.keys(obj) : undefined) : keys_ var length = index === 0 ? (keys ? keys.length : obj.length) : length_ if (index >= (length || 0)) { complete () } else { var key = keys ? keys[index] : index elem (obj[key], key, () => !completed && each.call (this, obj, elem_, complete_, index + 1, length, keys)) } } } }) } ) /* map ======================================================================== */ _.withTest (['cps', 'map'], function () { /* 2-argument iterator semantics */ _.cps.map ([7,6,5], function (x, then) { then (x + 1) }, function (result) { $assert (result, [8,7,6]) }) /* 3-argument iterator semantics */ _.cps.map ([7,6,5], function (x, i, then) { then (x + 1) }, function (result) { $assert (result, [8,7,6]) }) }, function () { _.extend (_.cps, { map: function (obj, iter, complete) { var result = _.isArray (obj) ? [] : {} _.cps.each (obj, (_.numArgs (iter) == 2) ? function (x, i, next) { iter (x, function (y) { result[i] = y; next () }) } : function (x, i, next) { iter (x, i, function (y) { result[i] = y; next () }) }, function () { complete (result) }) } }) }) /* find ======================================================================== */ _.withTest (['cps', 'find'], function () { /* Basic use */ _.cps.find ([7,6,5], function (x, then) { then ((x % 3) === 0) }, function (x, key) { $assert ([x, key], [6, 1]) }) /* Over dictionary */ _.cps.find ({ foo: 7, bar: 6, baz: 5 }, function (x, key, then) { then (key === 'baz') }, function (x, key) { $assert ([x, key], [5, 'baz']) }) /* Returning non-boolean */ _.cps.find ([7,6,5], function (x, then) { then (((x % 3) === 0) ? 'yeah' : false) }, function (x, key) { $assert ([x, key], ['yeah', 1]) }) /* Not found */ _.cps.find ([7,6,5], function (x, key, then) { then (false) }, function (x) { $assert (x, undefined) }) }, function () { _.extend (_.cps, { find: function (obj, pred, complete) { var passKey = (_.numArgs (pred) !== 2) _.cps.each (obj, function (x, key, next, complete) { var take = function (match) { if (match === false) { next () } else { complete (match === true ? x : match, key) } } if (passKey) { pred (x, key, take) } else { pred (x, take) } }, complete) } }) }) /* memoize ======================================================================== */ _.withTest (['cps', 'memoize'], function () { $assertEveryCalledOnce (function (noMoreThanOne) { var plusOne = _.cps.memoize (function (x, then) { noMoreThanOne (); then (x + 1) }) plusOne (2, function (x) { $assert (x === 3) }) plusOne (2, function (x) { $assert (x === 3) }) }) }, function () { _.extend (_.cps, { memoize: function (fn) { return _.barrier ? _.cps._betterMemoize (fn) : _.cps._poorMemoize (fn) }, /* This simplified version is used to bootstrap Useless.js code base (where _.barrier not available) */ _poorMemoize: function (fn) { var cache = {} return function (value, then) { if (value in cache) { // there's a flaw: cache updates after fetch completes, so while fetch is running, then (cache[value]) } // any subsequent call (until cache is ready) will trigger fetch (as it doesnt know that result is already fetching) else { fn.call (this, value, function (result) { then (cache[value] = result) }) } } }, /* UPD: added support for 0-arity semantics */ _betterMemoize: function (fn) { var cache = {} // barrier-enabled impl, eliminates redundant fetches // in this version, any subsequent calls join at barrier (which opens when result is fetched) switch (_.numArgs (fn)) { case 1: return function (then) { if (!cache.already) { fn.call (this, (cache = _.barrier ())) } cache (then) } case 2: cache = {} return function (value, then) { if (!(value in cache)) { fn.call (this, value, (cache[value] = _.barrier ())) } cache[value] (then) } default: throw new Error ('_.cps.memoize: unsupported number of arguments') } } }) }) /* reduce ======================================================================== */ _.withTest (['cps', 'reduce'], function () { $assertEveryCalled (function (mkay__2) { var input = [1,2,3] var sums = function (a, b, then) { then (a + b) } var check = function (result) { $assert (result === 6); mkay__2 () } _.cps.reduce (input, sums, check) _.cps.reduce ([], sums, check, 6) })}, function () { var reduce = function (array, op, then, memo, index) { // internal impl if (!array || (index >= (array.length || 0))) { then (memo) } else { op (memo, array[index], function (result) { reduce (array, op, then, result, index + 1) }) } } _.cps.reduce = function (array, op, then, memo) { // public API if (arguments.length < 4) { reduce (array, op, then, array[0], 1) } else { reduce (array, op, then, memo, 0) } } } ) /* noop / identity / constant ======================================================================== */ _.withTest (['cps', 'noop, identity, constant'], function () { $assertEveryCalled (function (noop, identity, const1, const2) { /* Port of underscore's _.noop to CPS terms */ _.cps.noop (1,2,3, function () { $assert (arguments.length === 0); noop () }) /* Port of underscore's _.identity to CPS terms */ _.cps.identity (1,2,3, function () { $assert ([1,2,3], _.asArray (arguments)); identity () }) /* Port of underscore's _.constant to CPS terms */ _.cps.constant (3) (function (_3) { $assert (_3 === 3); const1 () }) _.cps.constant (1, 2) (function (_1, _2) { $assert (_1 === 1); $assert (_2 === 2); const2 () }) })}, function () { _.extend (_.cps, { noop: $restArg (function () { return _.last (arguments).call (this) }), identity: $restArg (function () { var args = _.initial (arguments), then = _.last (arguments) if (then) { return then.apply (this, args) } }), constant: $restArg (function () { var args = arguments return function () { return _.last (arguments).apply (this, args) } }) })} ) /* arity ======================================================================== */ _.deferTest (['cps', 'arity / resultArity'], function () { var returnMyArgs = _.cps.identity var put123 = function (fn) { return _.partial (fn, 1,2,3) } $assertCPS (put123 ( returnMyArgs), [1,2,3]) $assertCPS (put123 (_.cps.arity2 (returnMyArgs)), [1,2]) $assertCPS (put123 (_.cps.arity1 (returnMyArgs)), [1]) $assertCPS (put123 (_.cps.arity0 (returnMyArgs))) var return123 = function (then) { then (1,2,3) } $assertCPS ( return123, [1,2,3]) $assertCPS (_.cps.resultArity2 (return123), [1,2]) $assertCPS (_.cps.resultArity1 (return123), [1]) $assertCPS (_.cps.resultArity0 (return123)) }, function () { _.cps.arity0 = function (fn) { return function () { fn.call (this, _.last (arguments)) } } _.cps.arity1 = function (fn) { return function () { fn.call (this, arguments[0], _.last (arguments)) } } _.cps.arity2 = function (fn) { return function () { fn.call (this, arguments[0], arguments[1], _.last (arguments)) } } _.cps.transformResult = function (operator, fn) { return function (args) { fn.apply (this, _.initial (arguments).concat (operator (_.last (arguments)))) } } _.cps.resultArity2 = _.partial (_.cps.transformResult, _.arity2) _.cps.resultArity1 = _.partial (_.cps.transformResult, _.arity1) _.cps.resultArity0 = _.partial (_.cps.transformResult, _.arity0) }) /* sequence / compose ======================================================================== */ _.withTest (['cps', 'sequence / compose'], function () { $assertEveryCalled (function (mkay__4) { /* Basic example of asynchronous functions sequencing */ var makeCookies = function (whatCookies, then) { then ('cookies ' + whatCookies) } var eatCookies = function (cookies, then) { then ('nice ' + cookies) } var check = function (result) { $assert (result, 'nice cookies from shit'); mkay__4 () } _.cps.sequence (makeCookies, eatCookies, check) ('from shit') // supports both ways (either argument list... _.cps.sequence ([makeCookies, eatCookies, check]) ('from shit') // ..or array _.cps (makeCookies, eatCookies, check) ('from shit') // shorthand macro /* A port of underscore's _.compose (simply flipped _.sequence) */ _.cps.compose (check, eatCookies, makeCookies) ('from shit') })}, function () { _.cps.sequence = $restArg ( function (arr) { var functions = (_.isArray (arr) && arr) || _.asArray (arguments) return _.reduceRight (functions, function (a, b) { return function () { return b.apply (this, _.asArray (arguments).concat (a)) }}, _.cps.identity) }) _.cps.compose = $restArg ( function (arr) { var functions = (_.isArray (arr) && arr) || _.asArray (arguments) return _.cps.sequence (functions.slice ().reverse ()) }) }) /* _.cps.sequence with error handling (kind of a simplified Promise) ======================================================================== */ _.deferTest (['cps', 'trySequence'], function () { var testErr = new Error () /* No error */ $assertEveryCalledOnce (function (mkay) { _.cps.trySequence ([ _.cps.constant ('foo'), _.appends ('bar').asContinuation], function (result) { $assert (result, 'foobar'); mkay () }) }) /* Throwing error */ $assertEveryCalledOnce (function (mkay) { _.cps.trySequence ([ function () { throw testErr }, function () { $fail }], function (result) { $assert (result === testErr); mkay () }) }) /* Returning error to continuation */ $assertEveryCalledOnce (function (mkay) { _.cps.trySequence ([ function (then) { then (testErr) }, function () { $fail }], function (result) { $assert (result === testErr); mkay () }) }) /* Reading error in separate callback */ $assertEveryCalledOnce (function (mkay) { _.cps.trySequence ([ function (then) { then (testErr) }, function () { $fail }], function (result) { $fail }, function (err) { $assert (err === testErr); mkay () }) }) }, function () { _.cps.trySequence = function (functions, then, err) { _.reduceRight (functions, function (a, b) { return function (e) { if (_.isTypeOf (Error, e)) { return (err || then) (e) } else { try { return b.apply (this, _.asArray (arguments).concat (a)) } catch (e) { return (err || then) (e) } } } }, then) () } })