├── .eslintrc.json ├── .github └── CODEOWNERS ├── .gitignore ├── .npmrc ├── README.md ├── async.js ├── await.js ├── bluebird-promisell.js ├── callbacks-revenge.js ├── callbacks.js ├── common ├── exit0.js ├── exit1.js ├── join.js ├── read-file-callback.js ├── read-file-promise.js ├── revengeutils.js └── sanctuary.js ├── coroutines-bluebird.js ├── coroutines-co.js ├── futures.js ├── input ├── bar.txt ├── baz.txt ├── foo.txt └── index.txt ├── most.js ├── node-streams.js ├── package.json ├── promises-pipe.js ├── promises-ramda.js ├── promises.js ├── righto.js ├── synchronous.js ├── tasks.js └── test /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": ["./node_modules/sanctuary-style/eslint-es6.json"], 4 | "parserOptions": {"ecmaVersion": 8}, 5 | "env": {"node": true}, 6 | "rules": { 7 | "max-len": ["error", {"code": 79, "ignoreComments": true}] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This is a CODEOWNERS file. 2 | # 3 | # Please consult https://help.github.com/articles/about-codeowners/ 4 | # for documentation. 5 | 6 | # Plaid codeowners requirements. 7 | * @plaid/developer-relations 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript has a problem 2 | 3 | We like to say that JavaScript has a callback problem. 4 | This project considers various approaches to the following problem: 5 | 6 | *Given a path to a directory containing an index file, __index.txt__, and 7 | zero or more other files, read the index file (which contains one filename 8 | per line), then read each of the files listed in the index concurrently, 9 | concat the resulting strings (in the order specified by the index), and 10 | write the result to stdout.* 11 | 12 | *The program's exit code should be 0 if the entire operation is successful; 13 | 1 otherwise.* 14 | 15 | All side effects must be isolated to the `main` function. Impure functions 16 | may be defined at the top level but may only be invoked in `main`. 17 | 18 | The solutions are best read in the following order: 19 | 20 | - [__synchronous.js__](./synchronous.js) 21 | - [__callbacks.js__](./callbacks.js) 22 | - [__node-streams.js__](./node-streams.js) 23 | - [__async.js__](./async.js) 24 | - [__righto.js__](./righto.js) 25 | - [__promises.js__](./promises.js) 26 | - [__promises-pipe.js__](./promises-pipe.js) 27 | - [__bluebird-promisell.js__](./bluebird-promisell.js) 28 | - [__most.js__](./most.js) 29 | - [__coroutines-co.js__](./coroutines-co.js) 30 | - [__coroutines-bluebird.js__](./coroutines-bluebird.js) 31 | - [__await.js__](./await.js) 32 | - [__futures.js__](./futures.js) 33 | - [__callbacks-revenge.js__](./callbacks-revenge.js) 34 | 35 | The above ordering is suggested reading order only – not grade. Solutions are 36 | graded on the following criteria: 37 | 38 | - the size of `main` (smaller is better); and 39 | - the degree to which the approach facilitates breaking the problem into 40 | manageable subproblems. 41 | 42 | --- 43 | 44 | † Perhaps in five years we'll be saying that JavaScript has a promise problem. 45 | -------------------------------------------------------------------------------- /async.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const async = require ('async'); 4 | const S = require ('sanctuary'); 5 | 6 | const exit0 = require ('./common/exit0'); 7 | const exit1 = require ('./common/exit1'); 8 | const join = require ('./common/join'); 9 | const readFile = require ('./common/read-file-callback'); 10 | 11 | 12 | const main = () => { 13 | const path = join (process.argv[2]); 14 | readFile (path ('index.txt')) ((err, data) => { 15 | if (err != null) exit1 (err); 16 | async.map ( 17 | S.map (path) (S.lines (data)), 18 | (filename, callback) => readFile (filename) (callback), 19 | (err, results) => { 20 | if (err == null) exit0 (S.joinWith ('') (results)); else exit1 (err); 21 | } 22 | ); 23 | }); 24 | }; 25 | 26 | if (process.mainModule.filename === __filename) main (); 27 | -------------------------------------------------------------------------------- /await.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const exit0 = require ('./common/exit0'); 4 | const exit1 = require ('./common/exit1'); 5 | const join = require ('./common/join'); 6 | const readFile = require ('./common/read-file-promise'); 7 | const S = require ('./common/sanctuary'); 8 | 9 | 10 | // concatFiles :: (String -> String) -> Promise Error String 11 | async function concatFiles(path) { 12 | const index = await readFile (path ('index.txt')); 13 | const filenames = S.map (path) (S.lines (index)); 14 | const results = await Promise.all (S.map (readFile) (filenames)); 15 | return S.joinWith ('') (results); 16 | } 17 | 18 | 19 | const main = () => { 20 | concatFiles (join (process.argv[2])) 21 | .then (exit0, exit1); 22 | }; 23 | 24 | if (process.mainModule.filename === __filename) main (); 25 | -------------------------------------------------------------------------------- /bluebird-promisell.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const P = require ('bluebird-promisell'); 4 | 5 | const exit0 = require ('./common/exit0'); 6 | const exit1 = require ('./common/exit1'); 7 | const join = require ('./common/join'); 8 | const readFile = require ('./common/read-file-promise'); 9 | const S = require ('./common/sanctuary'); 10 | 11 | 12 | // concatFiles :: (String -> String) -> Promise Error String 13 | const concatFiles = path => { 14 | // readFileRel :: String -> Promise Error String 15 | const readFileRel = S.compose (readFile) (path); 16 | // indexP :: Promise Error String 17 | const indexP = readFileRel ('index.txt'); 18 | // filenamesP :: Promise Error (Array String) 19 | const filenamesP = P.liftp1 (S.lines) (indexP); 20 | // resultsP :: Promise Error (Array String) 21 | const resultsP = P.liftp1 (P.traversep (readFileRel)) (filenamesP); 22 | // (return value) :: Promise Error String 23 | return P.liftp1 (S.joinWith ('')) (resultsP); 24 | }; 25 | 26 | 27 | const main = () => { 28 | concatFiles (join (process.argv[2])) 29 | .then (exit0, exit1); 30 | }; 31 | 32 | if (process.mainModule.filename === __filename) main (); 33 | -------------------------------------------------------------------------------- /callbacks-revenge.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require ('fs'); 4 | 5 | const exit0 = require ('./common/exit0'); 6 | const exit1 = require ('./common/exit1'); 7 | const { 8 | Arr, 9 | Fn, 10 | Str, 11 | Path, 12 | NodeEither, 13 | GenericEitherT, 14 | Cont, 15 | } = require ('./common/revengeutils'); 16 | 17 | const getpath = Path.combine (process.argv[2]); 18 | 19 | // A monad for working with continuations that contain NodeEithers 20 | // :: type NC r e x = GenericEither (Cont r) NodeEither e x 21 | // :: = Cont r (NodeEither e x) 22 | const NC = GenericEitherT (NodeEither) (Cont); 23 | 24 | // Standard NodeJS APIs need a little massaging to be valid continuations 25 | // - Callback must be a curried, final argument 26 | // - Can only pass a single argument to callback (an [err, ...data] array is fine) 27 | // :: Path -> NC () String String 28 | const readFile = path => cb => 29 | fs.readFile (path, {encoding: 'utf8'}, (...args) => cb (args)); 30 | 31 | // Main 32 | // :: String -> NC () String String 33 | const readAllFiles = Fn.pipe ([ 34 | getpath, // :: Path 35 | readFile, // :: NC () String String 36 | NC.map (Str.lines), // :: NC () String [String] 37 | NC.map (Arr.map (getpath)), // :: NC () String [Path] 38 | NC.chain (Arr.traverse (NC) (readFile)), // :: NC () String [String] 39 | NC.map (Str.join ('')), // :: NC () String String 40 | ]); 41 | 42 | // :: NC () String String 43 | const result = readAllFiles ('index.txt'); 44 | 45 | // Remember that: 46 | // :: type NC r e x = Cont r (NodeEither e x) 47 | // so... 48 | // :: NC () String String = Cont () (NodeEither String String) 49 | // :: = ((NodeEither String String) -> ()) -> () 50 | 51 | // :: NodeEither String String -> ⊥ 52 | const fork = NodeEither.match ({Left: exit1, Right: exit0}); 53 | const main = () => result (fork); // :: () 54 | main (); 55 | -------------------------------------------------------------------------------- /callbacks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const S = require ('sanctuary'); 4 | 5 | const exit0 = require ('./common/exit0'); 6 | const exit1 = require ('./common/exit1'); 7 | const join = require ('./common/join'); 8 | const readFile = require ('./common/read-file-callback'); 9 | 10 | 11 | const main = () => { 12 | const path = join (process.argv[2]); 13 | readFile (path ('index.txt')) ((err, data) => { 14 | if (err != null) exit1 (err); 15 | const filenames = S.lines (data); 16 | const $results = []; 17 | let filesRead = 0; 18 | filenames.forEach ((file, idx) => { 19 | readFile (path (file)) ((err, data) => { 20 | if (err != null) exit1 (err); 21 | $results[idx] = data; 22 | filesRead += 1; 23 | if (filesRead === filenames.length) exit0 (S.joinWith ('') ($results)); 24 | }); 25 | }); 26 | }); 27 | }; 28 | 29 | if (process.mainModule.filename === __filename) main (); 30 | -------------------------------------------------------------------------------- /common/exit0.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = s => { 4 | process.stdout.write (s); 5 | process.exit (0); 6 | }; 7 | -------------------------------------------------------------------------------- /common/exit1.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = err => { 4 | process.stderr.write (`${err}\n`); 5 | process.exit (1); 6 | }; 7 | -------------------------------------------------------------------------------- /common/join.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require ('path'); 4 | 5 | const S = require ('sanctuary'); 6 | 7 | // join :: String -> String -> String 8 | module.exports = S.curry2 (path.join); 9 | -------------------------------------------------------------------------------- /common/read-file-callback.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require ('fs'); 4 | 5 | // readFile :: String -> ((Error?, String?) -> Undefined) -> Undefined 6 | module.exports = filename => callback => 7 | fs.readFile (filename, {encoding: 'utf8'}, callback); 8 | -------------------------------------------------------------------------------- /common/read-file-promise.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require ('fs'); 4 | 5 | // readFile :: String -> Promise Error String 6 | module.exports = filename => 7 | new Promise ((res, rej) => { 8 | fs.readFile (filename, {encoding: 'utf8'}, (err, data) => { 9 | if (err == null) res (data); else rej (err); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /common/revengeutils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require ('path'); 4 | 5 | 6 | const derivemonad = M => { 7 | const {of, chain} = M; 8 | 9 | const map = f => chain (x => of (f (x))); 10 | const lift2 = f => mx => my => 11 | chain (x => chain (y => of (f (x) (y))) (my)) (mx); 12 | 13 | return Object.assign ({map, lift2}, M); 14 | }; 15 | 16 | const Arr = (() => { 17 | const of = x => [x]; 18 | const map = f => x => x.map (f); 19 | 20 | const foldl = f => z => xs => xs.reduce ((p, c) => f (p) (c), z); 21 | 22 | const empty = []; 23 | const append = a => b => [...a, ...b]; 24 | 25 | const sequence = A => 26 | foldl (A.lift2 (b => a => append (b) (of (a)))) (A.of (empty)); 27 | const traverse = A => f => xs => sequence (A) (map (f) (xs)); 28 | 29 | return {map, foldl, traverse}; 30 | }) (); 31 | 32 | const Fn = (() => { 33 | const id = x => x; 34 | const compose = f => g => a => f (g (a)); 35 | const flip = f => x => y => f (y) (x); 36 | const pipe = Arr.foldl (flip (compose)) (id); 37 | 38 | return {id, compose, flip, pipe, '.': compose}; 39 | }) (); 40 | 41 | const Str = (() => { 42 | const trim = s => s.trim (); 43 | const join = sep => arr => arr.join (sep); 44 | const split = sep => s => s.split (sep); 45 | const lines = Fn['.'] (split ('\n')) (trim); 46 | 47 | return {split, lines, join, trim}; 48 | }) (); 49 | 50 | const Path = (() => { 51 | const combine = base => sub => path.join (base, sub); 52 | return {combine}; 53 | }) (); 54 | 55 | // An Either interpretation of Node style first-element-falsy arrays 56 | // :: type NodeEither e d = [Maybe e, ...d] 57 | const NodeEither = (() => { 58 | const Left = l => [l]; 59 | const Right = (...r) => [null, ...r]; 60 | 61 | const match = ({Left, Right}) => ([e, ...x]) => e ? Left (e) : Right (...x); 62 | 63 | return {Left, Right, match}; 64 | }) (); 65 | 66 | // The GenericEitherT monad transformer 67 | // :: type GenericEitherT e m l r = m (e l r) 68 | const GenericEitherT = E => M => { 69 | const {Left, Right, match} = E; 70 | 71 | // :: x -> m (e l x) 72 | const of = x => M.of (Right (x)); 73 | 74 | // :: (a -> m (e l b)) -> m (e l a) -> m (e l b) 75 | const chain = f => 76 | Fn['.'] (M.chain) (match) ({ 77 | Left: l => M.of (Left (l)), 78 | Right: f, 79 | }); 80 | 81 | return derivemonad ({of, chain}); 82 | }; 83 | 84 | // The continuation monad 85 | // :: type Cont r a = (a -> r) -> r 86 | const Cont = (() => { 87 | const of = x => cb => cb (x); 88 | const chain = f => m => cb => m (x => f (x) (cb)); 89 | 90 | return derivemonad ({of, chain}); 91 | }) (); 92 | 93 | module.exports = {Arr, Fn, Str, Path, NodeEither, GenericEitherT, Cont}; 94 | -------------------------------------------------------------------------------- /common/sanctuary.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const FutureTypes = require ('fluture-sanctuary-types'); 4 | const S = require ('sanctuary'); 5 | const $ = require ('sanctuary-def'); 6 | 7 | 8 | // PromiseType :: Type 9 | const PromiseType = $.NullaryType 10 | ('Promise') 11 | ('') 12 | ([]) 13 | (x => x != null && x.constructor === Promise); 14 | 15 | module.exports = S.create ({ 16 | checkTypes: true, 17 | env: S.env.concat (FutureTypes.env.concat ([PromiseType])), 18 | }); 19 | -------------------------------------------------------------------------------- /coroutines-bluebird.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const bluebird = require ('bluebird'); 4 | 5 | const exit0 = require ('./common/exit0'); 6 | const exit1 = require ('./common/exit1'); 7 | const join = require ('./common/join'); 8 | const readFile = require ('./common/read-file-promise'); 9 | const S = require ('./common/sanctuary'); 10 | 11 | 12 | // concatFiles :: (String -> String) -> Promise Error String 13 | const concatFiles = bluebird.coroutine (function* generator(path) { 14 | const index = yield readFile (path ('index.txt')); 15 | const filenames = S.map (path) (S.lines (index)); 16 | const results = yield bluebird.all (S.map (readFile) (filenames)); 17 | return S.joinWith ('') (results); 18 | }); 19 | 20 | 21 | const main = () => { 22 | concatFiles (join (process.argv[2])) 23 | .then (exit0, exit1); 24 | }; 25 | 26 | if (process.mainModule.filename === __filename) main (); 27 | -------------------------------------------------------------------------------- /coroutines-co.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const co = require ('co'); 4 | 5 | const exit0 = require ('./common/exit0'); 6 | const exit1 = require ('./common/exit1'); 7 | const join = require ('./common/join'); 8 | const readFile = require ('./common/read-file-promise'); 9 | const S = require ('./common/sanctuary'); 10 | 11 | 12 | // concatFiles :: (String -> String) -> Promise Error String 13 | const concatFiles = path => 14 | co (function* generator() { 15 | const index = yield readFile (path ('index.txt')); 16 | const filenames = S.map (path) (S.lines (index)); 17 | const results = yield Promise.all (S.map (readFile) (filenames)); 18 | return S.joinWith ('') (results); 19 | }); 20 | 21 | 22 | const main = () => { 23 | concatFiles (join (process.argv[2])) 24 | .then (exit0, exit1); 25 | }; 26 | 27 | if (process.mainModule.filename === __filename) main (); 28 | -------------------------------------------------------------------------------- /futures.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require ('fs'); 4 | 5 | const Future = require ('fluture'); 6 | 7 | const exit0 = require ('./common/exit0'); 8 | const exit1 = require ('./common/exit1'); 9 | const join = require ('./common/join'); 10 | const S = require ('./common/sanctuary'); 11 | 12 | 13 | // readFile :: String -> Future Error String 14 | const readFile = S.flip (Future.encaseN2 (fs.readFile)) ({encoding: 'utf8'}); 15 | 16 | // readFilePar :: String -> ConcurrentFuture Error String 17 | const readFilePar = S.compose (Future.Par) (readFile); 18 | 19 | // concatFiles :: (String -> String) -> Future Error String 20 | const concatFiles = path => 21 | S.pipe ([path, // :: String 22 | readFile, // :: Future Error String 23 | S.map (S.lines), // :: Future Error (Array String) 24 | S.map (S.map (path)), // :: Future Error (Array String) 25 | S.map (S.traverse (Future.Par) (readFilePar)), // :: Future Error (ConcurrentFuture Error (Array String)) 26 | S.chain (Future.seq), // :: Future Error (Array String) 27 | S.map (S.joinWith (''))]) // :: Future Error String 28 | ('index.txt'); 29 | 30 | 31 | const main = () => { 32 | concatFiles (join (process.argv[2])) 33 | .fork (exit1, exit0); 34 | }; 35 | 36 | if (process.mainModule.filename === __filename) main (); 37 | -------------------------------------------------------------------------------- /input/bar.txt: -------------------------------------------------------------------------------- 1 | BAR 2 | -------------------------------------------------------------------------------- /input/baz.txt: -------------------------------------------------------------------------------- 1 | BAZ 2 | -------------------------------------------------------------------------------- /input/foo.txt: -------------------------------------------------------------------------------- 1 | FOO 2 | -------------------------------------------------------------------------------- /input/index.txt: -------------------------------------------------------------------------------- 1 | foo.txt 2 | bar.txt 3 | baz.txt 4 | -------------------------------------------------------------------------------- /most.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const most = require ('most'); 4 | 5 | const exit0 = require ('./common/exit0'); 6 | const exit1 = require ('./common/exit1'); 7 | const join = require ('./common/join'); 8 | const readFile = require ('./common/read-file-promise'); 9 | const S = require ('./common/sanctuary'); 10 | 11 | 12 | // concatFiles :: (String -> String) -> Promise Error String 13 | const concatFiles = path => 14 | most.fromPromise (readFile (path ('index.txt'))) 15 | .map (S.lines) 16 | .map (S.map (path)) 17 | .map (S.map (readFile)) 18 | .map (Promise.all.bind (Promise)) 19 | .awaitPromises () 20 | .map (S.joinWith ('')) 21 | .reduce ((x, y) => x + y, ''); 22 | 23 | 24 | const main = () => { 25 | concatFiles (join (process.argv[2])) 26 | .then (exit0, exit1); 27 | }; 28 | 29 | if (process.mainModule.filename === __filename) main (); 30 | -------------------------------------------------------------------------------- /node-streams.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require ('fs'); 4 | 5 | const miss = require ('mississippi'); 6 | const split = require ('split2'); 7 | 8 | const exit0 = require ('./common/exit0'); 9 | const exit1 = require ('./common/exit1'); 10 | const join = require ('./common/join'); 11 | const readFile = require ('./common/read-file-callback'); 12 | 13 | const main = () => { 14 | const path = join (process.argv[2]); 15 | let results; 16 | 17 | miss.pipe ( 18 | fs.createReadStream (path ('index.txt')), 19 | split (), 20 | miss.parallel (123, (filename, cb) => readFile (path (filename)) (cb)), 21 | miss.concat (data => { results = data; }), 22 | err => { if (err != null) exit1 (err); else exit0 (results); } 23 | ); 24 | }; 25 | 26 | if (process.mainModule.filename === __filename) main (); 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-problem", 3 | "private": true, 4 | "scripts": { 5 | "lint": "eslint -- \"**/*.js\"", 6 | "test": "./test" 7 | }, 8 | "dependencies": { 9 | "async": "3.1.0", 10 | "bluebird": "3.7.1", 11 | "bluebird-promisell": "0.7.0", 12 | "co": "4.6.0", 13 | "fluture": "11.0.3", 14 | "fluture-sanctuary-types": "4.1.1", 15 | "mississippi": "4.0.0", 16 | "most": "1.7.3", 17 | "righto": "6.0.0", 18 | "sanctuary": "2.0.0", 19 | "sanctuary-def": "0.20.0", 20 | "split2": "3.1.1" 21 | }, 22 | "devDependencies": { 23 | "eslint": "5.15.x", 24 | "sanctuary-style": "3.0.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /promises-pipe.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const exit0 = require ('./common/exit0'); 4 | const exit1 = require ('./common/exit1'); 5 | const join = require ('./common/join'); 6 | const readFile = require ('./common/read-file-promise'); 7 | const S = require ('./common/sanctuary'); 8 | 9 | 10 | // then :: (a -> (b | Promise e b)) -> Promise e a -> Promise e b 11 | const then = f => p => p.then (f); 12 | 13 | // concatFiles :: (String -> String) -> Promise Error String 14 | const concatFiles = path => 15 | S.pipe ([path, 16 | readFile, 17 | then (S.lines), 18 | then (S.map (path)), 19 | then (S.map (readFile)), 20 | then (Promise.all.bind (Promise)), 21 | then (S.joinWith (''))]) 22 | ('index.txt'); 23 | 24 | 25 | const main = () => { 26 | concatFiles (join (process.argv[2])) 27 | .then (exit0, exit1); 28 | }; 29 | 30 | if (process.mainModule.filename === __filename) main (); 31 | -------------------------------------------------------------------------------- /promises-ramda.js: -------------------------------------------------------------------------------- 1 | promises-pipe.js -------------------------------------------------------------------------------- /promises.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const exit0 = require ('./common/exit0'); 4 | const exit1 = require ('./common/exit1'); 5 | const join = require ('./common/join'); 6 | const readFile = require ('./common/read-file-promise'); 7 | const S = require ('./common/sanctuary'); 8 | 9 | 10 | // concatFiles :: (String -> String) -> Promise Error String 11 | const concatFiles = path => 12 | Promise.resolve ('index.txt') 13 | .then (path) 14 | .then (readFile) 15 | .then (S.lines) 16 | .then (S.map (path)) 17 | .then (S.map (readFile)) 18 | .then (Promise.all.bind (Promise)) 19 | .then (S.joinWith ('')); 20 | 21 | 22 | const main = () => { 23 | concatFiles (join (process.argv[2])) 24 | .then (exit0, exit1); 25 | }; 26 | 27 | if (process.mainModule.filename === __filename) main (); 28 | -------------------------------------------------------------------------------- /righto.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require ('fs'); 4 | 5 | const righto = require ('righto'); 6 | const S = require ('sanctuary'); 7 | 8 | const exit0 = require ('./common/exit0'); 9 | const exit1 = require ('./common/exit1'); 10 | const join = require ('./common/join'); 11 | 12 | 13 | // readFile :: String -> Righto String 14 | const readFile = filename => 15 | righto (fs.readFile, filename, {encoding: 'utf8'}); 16 | 17 | // concatFiles :: (String -> String) -> Righto String 18 | const concatFiles = path => { 19 | const readFileRel = S.compose (readFile) (path); 20 | const index = readFileRel ('index.txt'); 21 | const files = righto.sync (S.compose (S.map (readFileRel)) (S.lines), index); 22 | return righto.sync (S.joinWith (''), righto.all (files)); 23 | }; 24 | 25 | 26 | const main = () => { 27 | concatFiles 28 | (join (process.argv[2])) 29 | ((err, data) => { if (err == null) exit0 (data); else exit1 (err); }); 30 | }; 31 | 32 | if (process.mainModule.filename === __filename) main (); 33 | -------------------------------------------------------------------------------- /synchronous.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require ('fs'); 4 | 5 | const S = require ('sanctuary'); 6 | 7 | const exit0 = require ('./common/exit0'); 8 | const exit1 = require ('./common/exit1'); 9 | const join = require ('./common/join'); 10 | 11 | 12 | const readFile = filename => { 13 | try { 14 | return fs.readFileSync (filename, {encoding: 'utf8'}); 15 | } catch (err) { 16 | exit1 (err); 17 | } 18 | }; 19 | 20 | 21 | const main = () => { 22 | const path = join (process.argv[2]); 23 | S.pipe ([path, 24 | readFile, 25 | S.lines, 26 | S.map (path), 27 | S.map (readFile), 28 | S.joinWith (''), 29 | exit0]) 30 | ('index.txt'); 31 | }; 32 | 33 | if (process.mainModule.filename === __filename) main (); 34 | -------------------------------------------------------------------------------- /tasks.js: -------------------------------------------------------------------------------- 1 | futures.js -------------------------------------------------------------------------------- /test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | passed="\033[0;32mpassed\033[0m\n" 4 | failed="\033[0;31mfailed\033[0m\n" 5 | 6 | test() { 7 | local actual 8 | local expected 9 | 10 | printf "Testing \033[0;36m%s\033[0m...\n" "$1" 11 | 12 | printf %s "- correct directory name... " 13 | expected="$(printf "FOO\nBAR\nBAZ\n")" 14 | if ! actual="$(node "$1" input)" ; then 15 | printf "$failed" 16 | elif [[ "$actual" == "$expected" ]] ; then 17 | printf "$passed" 18 | else 19 | printf "$failed" 20 | fi 21 | 22 | printf %s "- incorrect directory name... " 23 | expected="^Error: ENOENT[,:] .* 'XXX/index.txt'$" 24 | if actual="$(node "$1" XXX 2>&1 1>/dev/null)" ; then 25 | printf "$failed" 26 | elif [[ $? != 1 ]] ; then 27 | printf "$failed" 28 | elif [[ "$actual" =~ $expected ]] ; then 29 | printf "$passed" 30 | else 31 | printf "$failed" 32 | fi 33 | 34 | printf %s "- inaccurate index... " 35 | rm -f -- input/baz.txt 36 | expected="^Error: ENOENT[,:] .* 'input/baz.txt'$" 37 | if actual="$(node "$1" input 2>&1 1>/dev/null)" ; then 38 | printf "$failed" 39 | elif [[ $? != 1 ]] ; then 40 | printf "$failed" 41 | elif [[ "$actual" =~ $expected ]] ; then 42 | printf "$passed" 43 | else 44 | printf "$failed" 45 | fi 46 | git checkout -- input/baz.txt 47 | 48 | echo 49 | } 50 | 51 | test synchronous.js 52 | test callbacks.js 53 | test node-streams.js 54 | test async.js 55 | test righto.js 56 | test promises.js 57 | test promises-pipe.js 58 | test bluebird-promisell.js 59 | test most.js 60 | test coroutines-co.js 61 | test coroutines-bluebird.js 62 | test await.js 63 | test futures.js 64 | test callbacks-revenge.js 65 | --------------------------------------------------------------------------------