├── .gitignore ├── README.md ├── database.json └── posts ├── behaviour-driven-development-with-javascript └── index.md ├── harder-better-faster-stronger-lo-dash-v3 ├── documentation-generator │ ├── generator.js │ ├── npm-debug.log │ └── package.json ├── documentation.md └── index.md ├── mysql-sinsert └── index.md ├── pro-angularjs └── index.md ├── the-definitive-guide-to-the-es7-async-functions └── index.md ├── the-definitive-guide-to-the-javascript-generators ├── generators.gif └── index.md ├── using-dataloader-to-batch-requests └── index.md └── using-mysql-in-node-js └── index.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Contents of the http://gajus.com/blog/. 2 | -------------------------------------------------------------------------------- /database.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": [ 3 | { 4 | "id": 1, 5 | "name": "Behaviour Driven Development with Javascript", 6 | "nid": "behaviour-driven-development-with-javascript", 7 | "published_at": "2014-09-24 14:37:34" 8 | }, 9 | { 10 | "id": 2, 11 | "name": "The Definitive Guide to the JavaScript Generators", 12 | "nid": "the-definitive-guide-to-the-javascript-generators", 13 | "published_at": "2014-10-04 10:58:48" 14 | }, 15 | { 16 | "id": 3, 17 | "name": "Pro AngularJS", 18 | "nid": "pro-angularjs", 19 | "published_at": null 20 | }, 21 | { 22 | "id": 4, 23 | "name": "Harder, Better, Faster, Stronger Lo-Dash v3", 24 | "nid": "harder-better-faster-stronger-lo-dash-v3", 25 | "published_at": "2015-01-20 14:46:53" 26 | }, 27 | { 28 | "id": 6, 29 | "name": "MySQL SINSERT", 30 | "nid": "mysql-sinsert", 31 | "published_at": "2015-04-02 18:18:20" 32 | }, 33 | { 34 | "id": 7, 35 | "name": "The Definitive Guide to the ES7 async functions", 36 | "nid": "the-definitive-guide-to-the-es7-async-functions", 37 | "published_at": null 38 | }, 39 | { 40 | "id": 8, 41 | "name": "Using MySQL in Node.js", 42 | "nid": "using-mysql-in-node-js", 43 | "published_at": "2016-04-24 17:37:38" 44 | }, 45 | { 46 | "id": 9, 47 | "name": "Using DataLoader to batch requests", 48 | "nid": "using-dataloader-to-batch-requests", 49 | "published_at": "2016-08-19 14:59:43" 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /posts/behaviour-driven-development-with-javascript/index.md: -------------------------------------------------------------------------------- 1 | This article is a result of extensive research about BDD in JavaScript. I have extracted the core principals and terminology, and provide practical examples that illustrate the benefits of BDD. 2 | 3 | ## What is BDD? 4 | 5 | In the 8th chapter, the author quotes various definitions of BDD. I favor the following: 6 | 7 |
8 |
9 |

[BDD] extends TDD by writing test cases in a natural language that non-programmers can read.

10 |
11 |
M. Manca
12 |
13 | 14 |
15 |
16 |

BDD is a second-generation, outside-in, pull-based, multiple-stakeholder, multiple-scale, high-automation, agile methodology. It describes a cycle of interactions with well-defined outputs, resulting in the delivery of working, tested software that matters.

17 |
18 |
Dan North
19 |
20 | 21 | Developers pull features from stakeholders and develop them outside-in. The features are pulled in the order of their business value. The most important feature is implemented first and it's implemented outside in. No time is wasted building unnecessary code. 22 | 23 | The evolution of a programmer that is learning TDD is a good guidance in understanding the BDD: 24 | 25 | 1. Most developers start by writing unit tests to existing code. 26 | 1. After some practise they learn about the advantages of writing the test first. They might learn about good testing practices like AAA, stubs and factories at this point. 27 | 1. They realise that TDD can be used as a design tool to discover the API of the production code. 28 | 1. Eventually they recognise that TDD is not about testing. It's about defining behavior and writing specifications that serve as living documentation. They also discover mocks and outside-in development. 29 | 30 | ## Principles of BDD 31 | 32 | ### Baby Steps 33 | 34 | Characteristic feature of BDD is using "baby steps" to achieve tight feedback loop. In practice, this means writing a single spec knowing that it will fail, writing the missing code logic, returning to the spec. This continues switching between code and test cases results in less scanning of the code. 35 | 36 | ### Red/Green/Refactor 37 | 38 | You want to achieve Red/Green/Refactor routine: 39 | 40 | 44 | 45 | The idea is to ensure that the test really works and can catch an error. 46 | 47 |

Triangulation

48 | 49 | Instead of filling in a real implementation, you can fake it with a dummy value. 50 | 51 | 52 | 53 | You know that your implementation isn't ready, but all the specs are showing green. This implies that there is a spec example missing. 54 | 55 | 56 | 57 | You proceed until you've covered just enough test cases to produce a general solution. 58 | 59 | 60 | 61 | The idea is to gain moment and insight into how the algorithm should behave. 62 | 63 | ### Summary 64 | 65 | Behaviour Driven Development is characterized by: 66 | 67 | * Spec/Error driven coding. 68 | * Using baby-steps to achieve a "tight feedback-loop". 69 | * Follow the Red-Green-Refactor (RGR) cycle to avoid the false-positives. 70 | 71 | The result: 72 | 73 | * Software design driven by needs. 74 | * RGR cycle enhances the separation of the workload. Red is for the interface, green is for the implementation. 75 | * Specs enable code refactoring. 76 | * Automated tests that read like documentation. Prefer DAMP over DRY. 77 | 78 |
79 |
Refactoring
80 |
Altering internal code structure without affecting the external behaviour.
81 | 82 |
DAMP
83 |
Descriptive and Meaningful Phrases.
84 |
85 | 86 | ## BDD in Practice 87 | 88 | ### Using Test Doubles 89 | 90 | #### Dummies 91 | 92 | Empty object that does nothing. 93 | 94 | * Objects are passed around but never actually used. Usually they are just used to fill parameter lists.[^http://martinfowler.com/articles/mocksArentStubs.html] 95 | 96 | 97 | 98 | #### Stubs 99 | 100 | Method designed just for testing. 101 | 102 | * Stub objects provide a valid response, but it's static – no matter what input you pass in, you'll always get the same response.[^http://stackoverflow.com/a/1830000/368691] 103 | * An object that provides predefined answers to method calls.[^http://stackoverflow.com/a/5180286/368691] 104 | * Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test. Stubs may also record information about calls, such as an email gateway stub that remembers the messages it 'sent', or maybe only how many messages it "sent".[^http://martinfowler.com/articles/mocksArentStubs.html] 105 | * A stub will never cause a test to fail.[^http://ayende.com/Wiki/Rhino+Mocks+3.5.ashx] 106 | * Stub is simple fake object. It just makes sure test runs smoothly.[^http://stackoverflow.com/a/3459431/368691] 107 | 108 | 109 | 110 | #### Mocks 111 | 112 | Object designed just for testing. 113 | 114 | * Mock objects are used in mock test cases – they validate that certain methods are called on those objects.[^http://stackoverflow.com/a/1830000/368691] 115 | * An object on which you set expectations.[^http://stackoverflow.com/a/5180286/368691] 116 | 117 | 118 | 119 | ### Example Factory 120 | 121 | Method used to instantiate the SUT object with canonical values, overwriting only the properties relevant to the test case. 122 | 123 | 124 | 125 | See https://github.com/petejkim/factory-lady, Object Mother and Test Data Builders. 126 | 127 | ### SEE Pattern 128 | 129 | Every spec needs these three parts and nothing more – in this order: 130 | 131 | 1. Setup, e.g. create an object. 132 | 1. Execute, i.e. introduce change to the object state. 133 | 1. Expect, i.e. state an expectation of what should have happened. 134 | 135 | You might have also heard Given, When, Then (GWT): 136 | 137 | * Given a condition. 138 | * When I do something. 139 | * Then I expect something to happen. 140 | 141 | The two are identical. The phrasing of the latter might be easier to comprehend. 142 | 143 | 144 | 145 | As a result, your specs must adhere to the following rules: 146 | 147 | * The spec must implement not more than one execution. 148 | * The spec must abstain from several expectations. 149 | * Do not use if-statement that lead to multiple execution and expectation branches – write several specs instead. 150 | 151 | In TDD world it is known as the Arrange, Act, Assert (AAA) pattern. 152 | 153 | ## Organizing Specs 154 | 155 | The specs are organised either per Feature or per Fixture. 156 | 157 | ### Per Feature 158 | 159 | This way of organizing a spec is also referred to as "by topic". 160 | 161 | The benefit of organizing your spec per feature makes it easier to write. 162 | 163 | 164 | 165 | ### Per Fixture 166 | 167 | This way or organizing a spec is also referred to as "by example data". 168 | 169 | The benefit of organizing your spec per fixture makes the resulting spec more descriptive and easier to read. The additional benefit is because all your examples share the same example data, you need to state it only once (e.g. using beforeEach setup function). 170 | 171 | 172 | 173 | ## High and Low Level Spec 174 | 175 | BDD project usually starts with writing the outer circle (high level specs) and then proceeding the implementation of the inner circle (low level specs). 176 | 177 | ### Outside-In Development 178 | 179 | You start with the implementation of the specs that have meaning to the business using hypothetical components (aka. acceptance test): 180 | 181 | 182 | 183 | Acceptance test is a high level spec that describes a scenario from the view of an application user. In contrast to the low level spec, a high level spec does not have an SUT. 184 | 185 | The upside of the outside-in development is that you never write code that will become redundant (the requirement can be traced back to the spec). The downside is that you cannot run the test cases until the implementation is complete. 186 | 187 | * They help to become aware of bugs – all spec violations are considered a bug (defect awareness). 188 | * They are most useful when shown to stakeholders. 189 | * There are usually only a few. they don't include all special cases. their main purpose is to provide an overview of the required functionality. 190 | 191 | ### Inside-Out Development 192 | 193 | You start with the basic components that make up the application: 194 | 195 | 196 | 197 | * They help to find specific bugs (localization). 198 | * They are most useful to the developers who maintain the code later. 199 | * There are usually a lot of low level specs since they need to cover all the low level details, special and edge cases. 200 | 201 | ## Further Read 202 | 203 | This post is a result of reading "Behaviour Driven Development with JavaScript by Marco Emrich"[^http://www.amazon.com/Behaviour-Driven-Development-JavaScript-introduction-ebook/dp/B00CYMN3J2] and the subsequent research. Another grate resource, although beyond JavaScript scope, is "BDD in Action: Behavior-driven development for the whole software lifecycle"[^http://www.amazon.com/BDD-Action-Behavior-driven-development-lifecycle/dp/161729165X/]. 204 | -------------------------------------------------------------------------------- /posts/harder-better-faster-stronger-lo-dash-v3/documentation-generator/generator.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'), 2 | Promise = require('bluebird'), 3 | request = require('request-promise'), 4 | commentParser = require('comment-parser'), 5 | fs = require('fs'), 6 | methods, 7 | lastGroup, 8 | docs = '', 9 | index = ''; 10 | 11 | methods = [ 12 | 'string/camelCase', 13 | 'string/capitalize', 14 | 'string/deburr', 15 | 'string/endsWith', 16 | 'string/escapeRegExp', 17 | 'string/kebabCase', 18 | 'string/pad', 19 | 'string/padLeft', 20 | 'string/padRight', 21 | 'string/repeat', 22 | 'string/snakeCase', 23 | 'string/startsWith', 24 | 'string/trim', 25 | 'string/trimLeft', 26 | 'string/trimRight', 27 | 'string/trunc', 28 | 'string/words', 29 | 'array/chunk', 30 | 'array/dropRight', 31 | 'array/dropRightWhile', 32 | 'array/dropWhile', 33 | 'array/flattenDeep', 34 | 'array/pullAt', 35 | 'array/slice', 36 | 'array/sortedLastIndex', 37 | 'array/takeRight', 38 | 'array/takeRightWhile', 39 | 'array/takeWhile', 40 | 'function/ary', 41 | 'function/before', 42 | 'function/curryRight', 43 | 'function/flow', 44 | 'function/negate', 45 | 'function/rearg', 46 | 'lang/isError', 47 | 'lang/isMatch', 48 | 'lang/isNative', 49 | 'lang/isTypedArray', 50 | 'lang/toPlainObject', 51 | 'utility/attempt', 52 | 'utility/matches', 53 | 'utility/propertyOf', 54 | 'collection/partition', 55 | 'collection/sortByAll', 56 | 'object/keysIn', 57 | 'object/valuesIn', 58 | 'chain/thru' 59 | ]; 60 | 61 | docs += '# Lo-Dash v3 Documentation\n\n'; 62 | docs += 'Lo-Dash v3 documentation generated from the source code.\n\n'; 63 | 64 | Promise 65 | .all(methods) 66 | .map(function (name) { 67 | return Promise.props({ 68 | name: name, 69 | body: request('https://raw.githubusercontent.com/lodash/lodash/es6/' + name + '.js') 70 | }) 71 | }) 72 | .each(function (method) { 73 | var comments, 74 | example, 75 | params, 76 | returns, 77 | body = method.body, 78 | group = method.name.split('/')[0], 79 | name = method.name.split('/')[1]; 80 | 81 | comments = commentParser(body); 82 | comments = comments[comments.length - 1]; 83 | 84 | if (lastGroup !== group) { 85 | docs += '## ' + group + '\n\n'; 86 | 87 | index += '\n### ' + group + '\n\n'; 88 | index += '| Name | Description |\n'; 89 | index += '| --- | --- |\n'; 90 | 91 | lastGroup = group; 92 | } 93 | 94 | index += '| [`' + name + '`](https://github.com/gajus/blog.gajus.com/blob/master/post/lodash-v3/documentation.md#' + name.toLowerCase() + ')| ' + comments.description.replace(/\n/g, ' ') + ' |\n'; 95 | 96 | docs += '### ' + name + '\n\n'; 97 | docs += 'https://raw.githubusercontent.com/lodash/lodash/es6/' + method.name + '.js\n\n'; 98 | docs += comments.description.split('\n').join('\n\n'); 99 | 100 | params = _.where(comments.tags, {tag: 'param'}); 101 | 102 | if (params.length) { 103 | docs += '\n\n#### Parameters\n\n'; 104 | 105 | docs += '| Name | Type | Description |\n'; 106 | docs += '| --- | --- | --- |\n'; 107 | 108 | params.forEach(function (param) { 109 | docs += '| `' + param.name + '` | `' + param.type + '` | ' + param.description + '|\n'; 110 | }); 111 | } 112 | 113 | returns = _.where(comments.tags, {tag: 'returns'}); 114 | 115 | if (returns.length) { 116 | docs += '\n\n#### Returns\n\n'; 117 | 118 | docs += '| Type | Description |\n'; 119 | docs += '| --- | --- |\n'; 120 | docs += '| `' + returns[0].type + '` | ' + returns[0].description + '|\n'; 121 | } 122 | 123 | example = _.where(comments.tags, {tag: 'example'}); 124 | 125 | if (example.length) { 126 | docs += '\n```js\n' + example[0].description + '\n```'; 127 | } 128 | 129 | docs += '\n\n'; 130 | }) 131 | .then(function () { 132 | fs.writeFileSync(__dirname + '/../documentation.md', docs); 133 | 134 | console.log(index); 135 | }); 136 | -------------------------------------------------------------------------------- /posts/harder-better-faster-stronger-lo-dash-v3/documentation-generator/npm-debug.log: -------------------------------------------------------------------------------- 1 | 0 info it worked if it ends with ok 2 | 1 verbose cli [ 'node', 3 | 1 verbose cli '/usr/bin/npm', 4 | 1 verbose cli 'install', 5 | 1 verbose cli 'git+https://github.com/lodash/lodash-cli.git' ] 6 | 2 info using npm@1.3.6 7 | 3 info using node@v0.10.26 8 | 4 warn package.json documentation-generator@ No repository field. 9 | 5 warn package.json documentation-generator@ No README data 10 | 6 verbose readDependencies using package.json deps 11 | 7 verbose cache add [ 'git+https://github.com/lodash/lodash-cli.git', null ] 12 | 8 verbose cache add name=undefined spec="git+https://github.com/lodash/lodash-cli.git" args=["git+https://github.com/lodash/lodash-cli.git",null] 13 | 9 verbose parsed url { protocol: 'git+https:', 14 | 9 verbose parsed url slashes: true, 15 | 9 verbose parsed url auth: null, 16 | 9 verbose parsed url host: 'github.com', 17 | 9 verbose parsed url port: null, 18 | 9 verbose parsed url hostname: 'github.com', 19 | 9 verbose parsed url hash: null, 20 | 9 verbose parsed url search: null, 21 | 9 verbose parsed url query: null, 22 | 9 verbose parsed url pathname: '/lodash/lodash-cli.git', 23 | 9 verbose parsed url path: '/lodash/lodash-cli.git', 24 | 9 verbose parsed url href: 'git+https://github.com/lodash/lodash-cli.git' } 25 | 10 silly lockFile fee0a640-github-com-lodash-lodash-cli-git git+https://github.com/lodash/lodash-cli.git 26 | 11 verbose lock git+https://github.com/lodash/lodash-cli.git /home/gajus/.npm/fee0a640-github-com-lodash-lodash-cli-git.lock 27 | 12 verbose addRemoteGit [ 'https://github.com/lodash/lodash-cli.git', 'master' ] 28 | 13 verbose git clone https://github.com/lodash/lodash-cli.git Initialized empty Git repository in /home/gajus/.npm/_git-remotes/https-github-com-lodash-lodash-cli-git-88736ad0/ 29 | 14 verbose git fetch -a origin (https://github.com/lodash/lodash-cli.git) 30 | 15 verbose git rev-list -n1 master f59acdc633d22d739e14a003621501a79f019f88 31 | 16 verbose resolved git url git+https://github.com/lodash/lodash-cli.git#f59acdc633d22d739e14a003621501a79f019f88 32 | 17 verbose tar unpack /home/gajus/tmp/npm-18894-R6zM1pk-/1421765463792-0.8938885827083141/tmp.tgz 33 | 18 silly lockFile 021425da-63792-0-8938885827083141-package tar:///home/gajus/tmp/npm-18894-R6zM1pk-/1421765463792-0.8938885827083141/package 34 | 19 verbose lock tar:///home/gajus/tmp/npm-18894-R6zM1pk-/1421765463792-0.8938885827083141/package /home/gajus/.npm/021425da-63792-0-8938885827083141-package.lock 35 | 20 silly lockFile 0a0a8e42-63792-0-8938885827083141-tmp-tgz tar:///home/gajus/tmp/npm-18894-R6zM1pk-/1421765463792-0.8938885827083141/tmp.tgz 36 | 21 verbose lock tar:///home/gajus/tmp/npm-18894-R6zM1pk-/1421765463792-0.8938885827083141/tmp.tgz /home/gajus/.npm/0a0a8e42-63792-0-8938885827083141-tmp-tgz.lock 37 | 22 silly gunzTarPerm modes [ '755', '644' ] 38 | 23 silly gunzTarPerm extractEntry 39 | 24 silly gunzTarPerm modified mode [ '', 509, 493 ] 40 | 25 silly gunzTarPerm extractEntry .gitattributes 41 | 26 silly gunzTarPerm modified mode [ '.gitattributes', 436, 420 ] 42 | 27 silly gunzTarPerm extractEntry .gitignore 43 | 28 silly gunzTarPerm modified mode [ '.gitignore', 436, 420 ] 44 | 29 silly gunzTarPerm extractEntry .travis.yml 45 | 30 silly gunzTarPerm modified mode [ '.travis.yml', 436, 420 ] 46 | 31 silly gunzTarPerm extractEntry CONTRIBUTING.md 47 | 32 silly gunzTarPerm modified mode [ 'CONTRIBUTING.md', 436, 420 ] 48 | 33 silly gunzTarPerm extractEntry LICENSE.txt 49 | 34 silly gunzTarPerm modified mode [ 'LICENSE.txt', 436, 420 ] 50 | 35 silly gunzTarPerm extractEntry README.md 51 | 36 silly gunzTarPerm modified mode [ 'README.md', 436, 420 ] 52 | 37 silly gunzTarPerm extractEntry bin/ 53 | 38 silly gunzTarPerm modified mode [ 'bin/', 509, 493 ] 54 | 39 silly gunzTarPerm extractEntry bin/lodash 55 | 40 silly gunzTarPerm modified mode [ 'bin/lodash', 436, 420 ] 56 | 41 silly gunzTarPerm extractEntry lib/ 57 | 42 silly gunzTarPerm modified mode [ 'lib/', 509, 493 ] 58 | 43 silly gunzTarPerm extractEntry lib/minify.js 59 | 44 silly gunzTarPerm modified mode [ 'lib/minify.js', 509, 493 ] 60 | 45 silly gunzTarPerm extractEntry lib/post-compile.js 61 | 46 silly gunzTarPerm modified mode [ 'lib/post-compile.js', 436, 420 ] 62 | 47 silly gunzTarPerm extractEntry lib/pre-compile.js 63 | 48 silly gunzTarPerm modified mode [ 'lib/pre-compile.js', 436, 420 ] 64 | 49 silly gunzTarPerm extractEntry lib/util.js 65 | 50 silly gunzTarPerm modified mode [ 'lib/util.js', 509, 493 ] 66 | 51 silly gunzTarPerm extractEntry npm-shrinkwrap.json 67 | 52 silly gunzTarPerm modified mode [ 'npm-shrinkwrap.json', 436, 420 ] 68 | 53 silly gunzTarPerm extractEntry package.json 69 | 54 silly gunzTarPerm modified mode [ 'package.json', 436, 420 ] 70 | 55 silly gunzTarPerm extractEntry template/ 71 | 56 silly gunzTarPerm modified mode [ 'template/', 509, 493 ] 72 | 57 silly gunzTarPerm extractEntry template/license.jst 73 | 58 silly gunzTarPerm modified mode [ 'template/license.jst', 436, 420 ] 74 | 59 silly gunzTarPerm extractEntry template/package.jst 75 | 60 silly gunzTarPerm modified mode [ 'template/package.jst', 436, 420 ] 76 | 61 silly gunzTarPerm extractEntry template/readme.jst 77 | 62 silly gunzTarPerm modified mode [ 'template/readme.jst', 436, 420 ] 78 | 63 silly gunzTarPerm extractEntry test/ 79 | 64 silly gunzTarPerm modified mode [ 'test/', 509, 493 ] 80 | 65 silly gunzTarPerm extractEntry test/fixture/ 81 | 66 silly gunzTarPerm modified mode [ 'test/fixture/', 509, 493 ] 82 | 67 silly gunzTarPerm extractEntry test/fixture/a.jst 83 | 68 silly gunzTarPerm modified mode [ 'test/fixture/a.jst', 436, 420 ] 84 | 69 silly gunzTarPerm extractEntry test/fixture/b.jst 85 | 70 silly gunzTarPerm modified mode [ 'test/fixture/b.jst', 436, 420 ] 86 | 71 silly gunzTarPerm extractEntry test/fixture/c.jst 87 | 72 silly gunzTarPerm modified mode [ 'test/fixture/c.jst', 436, 420 ] 88 | 73 silly gunzTarPerm extractEntry test/fixture/c/ 89 | 74 silly gunzTarPerm modified mode [ 'test/fixture/c/', 509, 493 ] 90 | 75 silly gunzTarPerm extractEntry test/fixture/c/c.jst 91 | 76 silly gunzTarPerm modified mode [ 'test/fixture/c/c.jst', 436, 420 ] 92 | 77 silly gunzTarPerm extractEntry test/fixture/d.jst 93 | 78 silly gunzTarPerm modified mode [ 'test/fixture/d.jst', 436, 420 ] 94 | 79 silly gunzTarPerm extractEntry test/fixture/e.jst 95 | 80 silly gunzTarPerm modified mode [ 'test/fixture/e.jst', 436, 420 ] 96 | 81 silly gunzTarPerm extractEntry test/fixture/f.tpl 97 | 82 silly gunzTarPerm modified mode [ 'test/fixture/f.tpl', 436, 420 ] 98 | 83 silly gunzTarPerm extractEntry test/npm/ 99 | 84 silly gunzTarPerm modified mode [ 'test/npm/', 509, 493 ] 100 | 85 silly gunzTarPerm extractEntry test/npm/index.js 101 | 86 silly gunzTarPerm modified mode [ 'test/npm/index.js', 436, 420 ] 102 | 87 silly gunzTarPerm extractEntry test/test.js 103 | 88 silly gunzTarPerm modified mode [ 'test/test.js', 436, 420 ] 104 | 89 silly lockFile 021425da-63792-0-8938885827083141-package tar:///home/gajus/tmp/npm-18894-R6zM1pk-/1421765463792-0.8938885827083141/package 105 | 90 silly lockFile 021425da-63792-0-8938885827083141-package tar:///home/gajus/tmp/npm-18894-R6zM1pk-/1421765463792-0.8938885827083141/package 106 | 91 silly lockFile 0a0a8e42-63792-0-8938885827083141-tmp-tgz tar:///home/gajus/tmp/npm-18894-R6zM1pk-/1421765463792-0.8938885827083141/tmp.tgz 107 | 92 silly lockFile 0a0a8e42-63792-0-8938885827083141-tmp-tgz tar:///home/gajus/tmp/npm-18894-R6zM1pk-/1421765463792-0.8938885827083141/tmp.tgz 108 | 93 verbose tar pack [ '/home/gajus/.npm/lodash-cli/3.0.0/package.tgz', 109 | 93 verbose tar pack '/home/gajus/tmp/npm-18894-R6zM1pk-/1421765463792-0.8938885827083141/package' ] 110 | 94 verbose tarball /home/gajus/.npm/lodash-cli/3.0.0/package.tgz 111 | 95 verbose folder /home/gajus/tmp/npm-18894-R6zM1pk-/1421765463792-0.8938885827083141/package 112 | 96 silly lockFile 021425da-63792-0-8938885827083141-package tar:///home/gajus/tmp/npm-18894-R6zM1pk-/1421765463792-0.8938885827083141/package 113 | 97 verbose lock tar:///home/gajus/tmp/npm-18894-R6zM1pk-/1421765463792-0.8938885827083141/package /home/gajus/.npm/021425da-63792-0-8938885827083141-package.lock 114 | 98 silly lockFile 01c44be2-npm-lodash-cli-3-0-0-package-tgz tar:///home/gajus/.npm/lodash-cli/3.0.0/package.tgz 115 | 99 verbose lock tar:///home/gajus/.npm/lodash-cli/3.0.0/package.tgz /home/gajus/.npm/01c44be2-npm-lodash-cli-3-0-0-package-tgz.lock 116 | 100 silly lockFile 021425da-63792-0-8938885827083141-package tar:///home/gajus/tmp/npm-18894-R6zM1pk-/1421765463792-0.8938885827083141/package 117 | 101 silly lockFile 021425da-63792-0-8938885827083141-package tar:///home/gajus/tmp/npm-18894-R6zM1pk-/1421765463792-0.8938885827083141/package 118 | 102 silly lockFile 01c44be2-npm-lodash-cli-3-0-0-package-tgz tar:///home/gajus/.npm/lodash-cli/3.0.0/package.tgz 119 | 103 silly lockFile 01c44be2-npm-lodash-cli-3-0-0-package-tgz tar:///home/gajus/.npm/lodash-cli/3.0.0/package.tgz 120 | 104 silly lockFile 87d2af8e-jus-npm-lodash-cli-3-0-0-package /home/gajus/.npm/lodash-cli/3.0.0/package 121 | 105 verbose lock /home/gajus/.npm/lodash-cli/3.0.0/package /home/gajus/.npm/87d2af8e-jus-npm-lodash-cli-3-0-0-package.lock 122 | 106 silly lockFile 87d2af8e-jus-npm-lodash-cli-3-0-0-package /home/gajus/.npm/lodash-cli/3.0.0/package 123 | 107 silly lockFile 87d2af8e-jus-npm-lodash-cli-3-0-0-package /home/gajus/.npm/lodash-cli/3.0.0/package 124 | 108 verbose tar unpack /home/gajus/.npm/lodash-cli/3.0.0/package.tgz 125 | 109 silly lockFile de585d58-jus-npm-lodash-cli-3-0-0-package tar:///home/gajus/.npm/lodash-cli/3.0.0/package 126 | 110 verbose lock tar:///home/gajus/.npm/lodash-cli/3.0.0/package /home/gajus/.npm/de585d58-jus-npm-lodash-cli-3-0-0-package.lock 127 | 111 silly lockFile 01c44be2-npm-lodash-cli-3-0-0-package-tgz tar:///home/gajus/.npm/lodash-cli/3.0.0/package.tgz 128 | 112 verbose lock tar:///home/gajus/.npm/lodash-cli/3.0.0/package.tgz /home/gajus/.npm/01c44be2-npm-lodash-cli-3-0-0-package-tgz.lock 129 | 113 silly gunzTarPerm modes [ '755', '644' ] 130 | 114 silly gunzTarPerm extractEntry package.json 131 | 115 silly gunzTarPerm extractEntry README.md 132 | 116 silly gunzTarPerm extractEntry LICENSE.txt 133 | 117 silly gunzTarPerm extractEntry bin/lodash 134 | 118 silly gunzTarPerm extractEntry lib/minify.js 135 | 119 silly gunzTarPerm extractEntry lib/post-compile.js 136 | 120 silly gunzTarPerm extractEntry lib/pre-compile.js 137 | 121 silly gunzTarPerm extractEntry lib/util.js 138 | 122 silly gunzTarPerm extractEntry template/license.jst 139 | 123 silly gunzTarPerm extractEntry template/package.jst 140 | 124 silly gunzTarPerm extractEntry template/readme.jst 141 | 125 silly lockFile de585d58-jus-npm-lodash-cli-3-0-0-package tar:///home/gajus/.npm/lodash-cli/3.0.0/package 142 | 126 silly lockFile de585d58-jus-npm-lodash-cli-3-0-0-package tar:///home/gajus/.npm/lodash-cli/3.0.0/package 143 | 127 silly lockFile 01c44be2-npm-lodash-cli-3-0-0-package-tgz tar:///home/gajus/.npm/lodash-cli/3.0.0/package.tgz 144 | 128 silly lockFile 01c44be2-npm-lodash-cli-3-0-0-package-tgz tar:///home/gajus/.npm/lodash-cli/3.0.0/package.tgz 145 | 129 verbose chmod /home/gajus/.npm/lodash-cli/3.0.0/package.tgz 644 146 | 130 verbose chown /home/gajus/.npm/lodash-cli/3.0.0/package.tgz [ 502, 503 ] 147 | 131 silly lockFile 88736ad0-github-com-lodash-lodash-cli-git https://github.com/lodash/lodash-cli.git 148 | 132 silly resolved [ { name: 'lodash-cli', 149 | 132 silly resolved version: '3.0.0', 150 | 132 silly resolved description: 'The Lo-Dash command-line interface.', 151 | 132 silly resolved homepage: 'https://lodash.com/custom-builds', 152 | 132 silly resolved license: 'MIT', 153 | 132 silly resolved main: 'bin/lodash', 154 | 132 silly resolved bin: { lodash: 'bin/lodash' }, 155 | 132 silly resolved keywords: [ 'builder', 'compile', 'customize', 'lodash', 'util' ], 156 | 132 silly resolved author: 157 | 132 silly resolved { name: 'John-David Dalton', 158 | 132 silly resolved email: 'john.david.dalton@gmail.com', 159 | 132 silly resolved url: 'http://allyoucanleet.com/' }, 160 | 132 silly resolved contributors: [ [Object], [Object], [Object], [Object], [Object] ], 161 | 132 silly resolved repository: { type: 'git', url: 'lodash/lodash-cli' }, 162 | 132 silly resolved scripts: { test: 'echo "See the repository CONTRIBUTING.md for testing instructions."' }, 163 | 132 silly resolved dependencies: 164 | 132 silly resolved { 'closure-compiler': '0.2.6', 165 | 132 silly resolved glob: '~4.3.5', 166 | 132 silly resolved 'lodash-compat': '3.0.0', 167 | 132 silly resolved 'uglify-js': '2.4.16' }, 168 | 132 silly resolved devDependencies: { 'qunit-extras': '~1.4.1', qunitjs: '~1.16.0' }, 169 | 132 silly resolved files: 170 | 132 silly resolved [ 'LICENSE.txt', 171 | 132 silly resolved 'bin/lodash', 172 | 132 silly resolved 'lib/minify.js', 173 | 132 silly resolved 'lib/pre-compile.js', 174 | 132 silly resolved 'lib/post-compile.js', 175 | 132 silly resolved 'lib/util.js', 176 | 132 silly resolved 'template/license.jst', 177 | 132 silly resolved 'template/package.jst', 178 | 132 silly resolved 'template/readme.jst' ], 179 | 132 silly resolved readme: '# lodash-cli v3.0.0\n\nThe [Lo-Dash](https://lodash.com/) command-line interface for creating custom builds & precompiling templates.\n\n## Dive in\n\nCheck out the [Lo-Dash custom builds documentation](https://lodash.com/custom-builds) for build commands & usage examples.
\nThe changelog for this release is available on our [wiki](https://github.com/lodash/lodash-cli/wiki/Changelog).\n\n## Installation\n\nUsing npm:\n\n```bash\n$ {sudo -H} npm i -g npm\n$ {sudo -H} npm i -g lodash-cli\n$ lodash -h\n```\n\n## Support\n\nTested in Node.js 0.8.28 & 0.10.35.\n\n**Note:** Node.js 0.10.8-0.10.11 [have](https://github.com/joyent/node/issues/5622) [bugs](https://github.com/joyent/node/issues/5688) preventing minified builds.\n', 180 | 132 silly resolved readmeFilename: 'README.md', 181 | 132 silly resolved _id: 'lodash-cli@3.0.0', 182 | 132 silly resolved dist: { shasum: '5ed82e86540b223893bdee6f5a9b741c6d4409df' }, 183 | 132 silly resolved _resolved: 'git+https://github.com/lodash/lodash-cli.git#f59acdc633d22d739e14a003621501a79f019f88', 184 | 132 silly resolved _from: 'git+https://github.com/lodash/lodash-cli.git' } ] 185 | 133 info install lodash-cli@3.0.0 into /var/www/dev/gajus kuizinas/2014 02 02 gajus.com/_blog/post/lodash-v3/documentation-generator 186 | 134 info installOne lodash-cli@3.0.0 187 | 135 info /var/www/dev/gajus kuizinas/2014 02 02 gajus.com/_blog/post/lodash-v3/documentation-generator/node_modules/lodash-cli unbuild 188 | 136 verbose tar unpack /home/gajus/.npm/lodash-cli/3.0.0/package.tgz 189 | 137 silly lockFile 7e402026-enerator-node-modules-lodash-cli tar:///var/www/dev/gajus kuizinas/2014 02 02 gajus.com/_blog/post/lodash-v3/documentation-generator/node_modules/lodash-cli 190 | 138 verbose lock tar:///var/www/dev/gajus kuizinas/2014 02 02 gajus.com/_blog/post/lodash-v3/documentation-generator/node_modules/lodash-cli /home/gajus/.npm/7e402026-enerator-node-modules-lodash-cli.lock 191 | 139 silly lockFile 01c44be2-npm-lodash-cli-3-0-0-package-tgz tar:///home/gajus/.npm/lodash-cli/3.0.0/package.tgz 192 | 140 verbose lock tar:///home/gajus/.npm/lodash-cli/3.0.0/package.tgz /home/gajus/.npm/01c44be2-npm-lodash-cli-3-0-0-package-tgz.lock 193 | 141 silly gunzTarPerm modes [ '755', '644' ] 194 | 142 silly gunzTarPerm extractEntry package.json 195 | 143 silly gunzTarPerm extractEntry README.md 196 | 144 silly gunzTarPerm extractEntry LICENSE.txt 197 | 145 silly gunzTarPerm extractEntry bin/lodash 198 | 146 silly gunzTarPerm extractEntry lib/minify.js 199 | 147 silly gunzTarPerm extractEntry lib/post-compile.js 200 | 148 silly gunzTarPerm extractEntry lib/pre-compile.js 201 | 149 silly gunzTarPerm extractEntry lib/util.js 202 | 150 silly gunzTarPerm extractEntry template/license.jst 203 | 151 silly gunzTarPerm extractEntry template/package.jst 204 | 152 silly gunzTarPerm extractEntry template/readme.jst 205 | 153 silly lockFile 7e402026-enerator-node-modules-lodash-cli tar:///var/www/dev/gajus kuizinas/2014 02 02 gajus.com/_blog/post/lodash-v3/documentation-generator/node_modules/lodash-cli 206 | 154 silly lockFile 7e402026-enerator-node-modules-lodash-cli tar:///var/www/dev/gajus kuizinas/2014 02 02 gajus.com/_blog/post/lodash-v3/documentation-generator/node_modules/lodash-cli 207 | 155 silly lockFile 01c44be2-npm-lodash-cli-3-0-0-package-tgz tar:///home/gajus/.npm/lodash-cli/3.0.0/package.tgz 208 | 156 silly lockFile 01c44be2-npm-lodash-cli-3-0-0-package-tgz tar:///home/gajus/.npm/lodash-cli/3.0.0/package.tgz 209 | 157 info preinstall lodash-cli@3.0.0 210 | 158 verbose readDependencies using package.json deps 211 | 159 verbose readDependencies using package.json deps 212 | 160 verbose cache add [ 'closure-compiler@0.2.6', null ] 213 | 161 verbose cache add name=undefined spec="closure-compiler@0.2.6" args=["closure-compiler@0.2.6",null] 214 | 162 verbose parsed url { protocol: null, 215 | 162 verbose parsed url slashes: null, 216 | 162 verbose parsed url auth: null, 217 | 162 verbose parsed url host: null, 218 | 162 verbose parsed url port: null, 219 | 162 verbose parsed url hostname: null, 220 | 162 verbose parsed url hash: null, 221 | 162 verbose parsed url search: null, 222 | 162 verbose parsed url query: null, 223 | 162 verbose parsed url pathname: 'closure-compiler@0.2.6', 224 | 162 verbose parsed url path: 'closure-compiler@0.2.6', 225 | 162 verbose parsed url href: 'closure-compiler@0.2.6' } 226 | 163 verbose cache add name="closure-compiler" spec="0.2.6" args=["closure-compiler","0.2.6"] 227 | 164 verbose parsed url { protocol: null, 228 | 164 verbose parsed url slashes: null, 229 | 164 verbose parsed url auth: null, 230 | 164 verbose parsed url host: null, 231 | 164 verbose parsed url port: null, 232 | 164 verbose parsed url hostname: null, 233 | 164 verbose parsed url hash: null, 234 | 164 verbose parsed url search: null, 235 | 164 verbose parsed url query: null, 236 | 164 verbose parsed url pathname: '0.2.6', 237 | 164 verbose parsed url path: '0.2.6', 238 | 164 verbose parsed url href: '0.2.6' } 239 | 165 verbose addNamed [ 'closure-compiler', '0.2.6' ] 240 | 166 verbose addNamed [ '0.2.6', '0.2.6' ] 241 | 167 silly lockFile e0e0009e-closure-compiler-0-2-6 closure-compiler@0.2.6 242 | 168 verbose lock closure-compiler@0.2.6 /home/gajus/.npm/e0e0009e-closure-compiler-0-2-6.lock 243 | 169 verbose cache add [ 'glob@~4.3.5', null ] 244 | 170 verbose cache add name=undefined spec="glob@~4.3.5" args=["glob@~4.3.5",null] 245 | 171 verbose parsed url { protocol: null, 246 | 171 verbose parsed url slashes: null, 247 | 171 verbose parsed url auth: null, 248 | 171 verbose parsed url host: null, 249 | 171 verbose parsed url port: null, 250 | 171 verbose parsed url hostname: null, 251 | 171 verbose parsed url hash: null, 252 | 171 verbose parsed url search: null, 253 | 171 verbose parsed url query: null, 254 | 171 verbose parsed url pathname: 'glob@~4.3.5', 255 | 171 verbose parsed url path: 'glob@~4.3.5', 256 | 171 verbose parsed url href: 'glob@~4.3.5' } 257 | 172 verbose cache add name="glob" spec="~4.3.5" args=["glob","~4.3.5"] 258 | 173 verbose parsed url { protocol: null, 259 | 173 verbose parsed url slashes: null, 260 | 173 verbose parsed url auth: null, 261 | 173 verbose parsed url host: null, 262 | 173 verbose parsed url port: null, 263 | 173 verbose parsed url hostname: null, 264 | 173 verbose parsed url hash: null, 265 | 173 verbose parsed url search: null, 266 | 173 verbose parsed url query: null, 267 | 173 verbose parsed url pathname: '~4.3.5', 268 | 173 verbose parsed url path: '~4.3.5', 269 | 173 verbose parsed url href: '~4.3.5' } 270 | 174 verbose addNamed [ 'glob', '~4.3.5' ] 271 | 175 verbose addNamed [ null, '>=4.3.5-0 <4.4.0-0' ] 272 | 176 silly lockFile 85d3ad16-glob-4-3-5 glob@~4.3.5 273 | 177 verbose lock glob@~4.3.5 /home/gajus/.npm/85d3ad16-glob-4-3-5.lock 274 | 178 verbose cache add [ 'lodash-compat@3.0.0', null ] 275 | 179 verbose cache add name=undefined spec="lodash-compat@3.0.0" args=["lodash-compat@3.0.0",null] 276 | 180 verbose parsed url { protocol: null, 277 | 180 verbose parsed url slashes: null, 278 | 180 verbose parsed url auth: null, 279 | 180 verbose parsed url host: null, 280 | 180 verbose parsed url port: null, 281 | 180 verbose parsed url hostname: null, 282 | 180 verbose parsed url hash: null, 283 | 180 verbose parsed url search: null, 284 | 180 verbose parsed url query: null, 285 | 180 verbose parsed url pathname: 'lodash-compat@3.0.0', 286 | 180 verbose parsed url path: 'lodash-compat@3.0.0', 287 | 180 verbose parsed url href: 'lodash-compat@3.0.0' } 288 | 181 verbose cache add name="lodash-compat" spec="3.0.0" args=["lodash-compat","3.0.0"] 289 | 182 verbose parsed url { protocol: null, 290 | 182 verbose parsed url slashes: null, 291 | 182 verbose parsed url auth: null, 292 | 182 verbose parsed url host: null, 293 | 182 verbose parsed url port: null, 294 | 182 verbose parsed url hostname: null, 295 | 182 verbose parsed url hash: null, 296 | 182 verbose parsed url search: null, 297 | 182 verbose parsed url query: null, 298 | 182 verbose parsed url pathname: '3.0.0', 299 | 182 verbose parsed url path: '3.0.0', 300 | 182 verbose parsed url href: '3.0.0' } 301 | 183 verbose addNamed [ 'lodash-compat', '3.0.0' ] 302 | 184 verbose addNamed [ '3.0.0', '3.0.0' ] 303 | 185 silly lockFile 3c21c534-lodash-compat-3-0-0 lodash-compat@3.0.0 304 | 186 verbose lock lodash-compat@3.0.0 /home/gajus/.npm/3c21c534-lodash-compat-3-0-0.lock 305 | 187 verbose cache add [ 'uglify-js@2.4.16', null ] 306 | 188 verbose cache add name=undefined spec="uglify-js@2.4.16" args=["uglify-js@2.4.16",null] 307 | 189 verbose parsed url { protocol: null, 308 | 189 verbose parsed url slashes: null, 309 | 189 verbose parsed url auth: null, 310 | 189 verbose parsed url host: null, 311 | 189 verbose parsed url port: null, 312 | 189 verbose parsed url hostname: null, 313 | 189 verbose parsed url hash: null, 314 | 189 verbose parsed url search: null, 315 | 189 verbose parsed url query: null, 316 | 189 verbose parsed url pathname: 'uglify-js@2.4.16', 317 | 189 verbose parsed url path: 'uglify-js@2.4.16', 318 | 189 verbose parsed url href: 'uglify-js@2.4.16' } 319 | 190 verbose cache add name="uglify-js" spec="2.4.16" args=["uglify-js","2.4.16"] 320 | 191 verbose parsed url { protocol: null, 321 | 191 verbose parsed url slashes: null, 322 | 191 verbose parsed url auth: null, 323 | 191 verbose parsed url host: null, 324 | 191 verbose parsed url port: null, 325 | 191 verbose parsed url hostname: null, 326 | 191 verbose parsed url hash: null, 327 | 191 verbose parsed url search: null, 328 | 191 verbose parsed url query: null, 329 | 191 verbose parsed url pathname: '2.4.16', 330 | 191 verbose parsed url path: '2.4.16', 331 | 191 verbose parsed url href: '2.4.16' } 332 | 192 verbose addNamed [ 'uglify-js', '2.4.16' ] 333 | 193 verbose addNamed [ '2.4.16', '2.4.16' ] 334 | 194 silly lockFile 20fcf559-uglify-js-2-4-16 uglify-js@2.4.16 335 | 195 verbose lock uglify-js@2.4.16 /home/gajus/.npm/20fcf559-uglify-js-2-4-16.lock 336 | 196 silly addNameRange { name: 'glob', range: '>=4.3.5-0 <4.4.0-0', hasData: false } 337 | 197 verbose url raw lodash-compat/3.0.0 338 | 198 verbose url resolving [ 'https://registry.npmjs.org/', './lodash-compat/3.0.0' ] 339 | 199 verbose url resolved https://registry.npmjs.org/lodash-compat/3.0.0 340 | 200 info trying registry request attempt 1 at 14:51:04 341 | 201 http GET https://registry.npmjs.org/lodash-compat/3.0.0 342 | 202 verbose url raw closure-compiler/0.2.6 343 | 203 verbose url resolving [ 'https://registry.npmjs.org/', './closure-compiler/0.2.6' ] 344 | 204 verbose url resolved https://registry.npmjs.org/closure-compiler/0.2.6 345 | 205 info trying registry request attempt 1 at 14:51:04 346 | 206 verbose etag "I3D7KGEM7LAZWJWSOCFGJJSS" 347 | 207 http GET https://registry.npmjs.org/closure-compiler/0.2.6 348 | 208 verbose url raw glob 349 | 209 verbose url resolving [ 'https://registry.npmjs.org/', './glob' ] 350 | 210 verbose url resolved https://registry.npmjs.org/glob 351 | 211 info trying registry request attempt 1 at 14:51:04 352 | 212 verbose etag "SRRWGGPL6V94MIR6QKPMBZHL" 353 | 213 http GET https://registry.npmjs.org/glob 354 | 214 verbose url raw uglify-js/2.4.16 355 | 215 verbose url resolving [ 'https://registry.npmjs.org/', './uglify-js/2.4.16' ] 356 | 216 verbose url resolved https://registry.npmjs.org/uglify-js/2.4.16 357 | 217 info trying registry request attempt 1 at 14:51:04 358 | 218 verbose etag "15GXROFMIO0K0BMM3T3RH5IS3" 359 | 219 http GET https://registry.npmjs.org/uglify-js/2.4.16 360 | 220 http 304 https://registry.npmjs.org/glob 361 | 221 silly registry.get cb [ 304, 362 | 221 silly registry.get { date: 'Tue, 20 Jan 2015 14:51:04 GMT', 363 | 221 silly registry.get server: 'Apache', 364 | 221 silly registry.get via: '1.1 varnish', 365 | 221 silly registry.get 'last-modified': 'Tue, 20 Jan 2015 14:51:04 GMT', 366 | 221 silly registry.get 'cache-control': 'max-age=60', 367 | 221 silly registry.get etag: '"SRRWGGPL6V94MIR6QKPMBZHL"', 368 | 221 silly registry.get age: '2', 369 | 221 silly registry.get 'x-served-by': 'cache-ams4123-AMS', 370 | 221 silly registry.get 'x-cache': 'HIT', 371 | 221 silly registry.get 'x-cache-hits': '1', 372 | 221 silly registry.get 'x-timer': 'S1421765464.054062,VS0,VE0', 373 | 221 silly registry.get vary: 'Accept', 374 | 221 silly registry.get 'content-length': '0', 375 | 221 silly registry.get 'keep-alive': 'timeout=10, max=50', 376 | 221 silly registry.get connection: 'Keep-Alive' } ] 377 | 222 verbose etag glob from cache 378 | 223 silly addNameRange number 2 { name: 'glob', range: '>=4.3.5-0 <4.4.0-0', hasData: true } 379 | 224 silly addNameRange versions [ 'glob', 380 | 224 silly addNameRange [ '1.1.0', 381 | 224 silly addNameRange '2.0.9', 382 | 224 silly addNameRange '2.0.8', 383 | 224 silly addNameRange '2.0.7', 384 | 224 silly addNameRange '2.1.0', 385 | 224 silly addNameRange '3.0.0', 386 | 224 silly addNameRange '3.0.1', 387 | 224 silly addNameRange '3.1.0', 388 | 224 silly addNameRange '3.1.1', 389 | 224 silly addNameRange '3.1.2', 390 | 224 silly addNameRange '3.1.3', 391 | 224 silly addNameRange '3.1.4', 392 | 224 silly addNameRange '3.1.5', 393 | 224 silly addNameRange '3.1.6', 394 | 224 silly addNameRange '3.1.7', 395 | 224 silly addNameRange '3.1.9', 396 | 224 silly addNameRange '3.1.10', 397 | 224 silly addNameRange '3.1.11', 398 | 224 silly addNameRange '3.1.12', 399 | 224 silly addNameRange '3.1.13', 400 | 224 silly addNameRange '3.1.14', 401 | 224 silly addNameRange '3.1.15', 402 | 224 silly addNameRange '3.1.16', 403 | 224 silly addNameRange '3.1.17', 404 | 224 silly addNameRange '3.1.18', 405 | 224 silly addNameRange '3.1.19', 406 | 224 silly addNameRange '3.1.20', 407 | 224 silly addNameRange '3.1.21', 408 | 224 silly addNameRange '3.2.0', 409 | 224 silly addNameRange '3.2.1', 410 | 224 silly addNameRange '3.2.3', 411 | 224 silly addNameRange '3.2.4', 412 | 224 silly addNameRange '3.2.5', 413 | 224 silly addNameRange '3.2.6', 414 | 224 silly addNameRange '3.2.7', 415 | 224 silly addNameRange '3.2.8', 416 | 224 silly addNameRange '3.2.9', 417 | 224 silly addNameRange '3.2.10', 418 | 224 silly addNameRange '3.2.11', 419 | 224 silly addNameRange '4.0.0', 420 | 224 silly addNameRange '4.0.1', 421 | 224 silly addNameRange '4.0.2', 422 | 224 silly addNameRange '4.0.3', 423 | 224 silly addNameRange '4.0.4', 424 | 224 silly addNameRange '4.0.5', 425 | 224 silly addNameRange '4.0.6', 426 | 224 silly addNameRange '4.1.2-beta', 427 | 224 silly addNameRange '4.1.2', 428 | 224 silly addNameRange '4.1.3', 429 | 224 silly addNameRange '4.1.4', 430 | 224 silly addNameRange '4.1.5', 431 | 224 silly addNameRange '4.1.6', 432 | 224 silly addNameRange '4.2.0', 433 | 224 silly addNameRange '4.2.1', 434 | 224 silly addNameRange '4.2.2', 435 | 224 silly addNameRange '4.3.0', 436 | 224 silly addNameRange '4.3.1', 437 | 224 silly addNameRange '4.3.2', 438 | 224 silly addNameRange '4.3.3', 439 | 224 silly addNameRange '4.3.4', 440 | 224 silly addNameRange '4.3.5' ] ] 441 | 225 verbose addNamed [ 'glob', '4.3.5' ] 442 | 226 verbose addNamed [ '4.3.5', '4.3.5' ] 443 | 227 silly lockFile 88cb2229-glob-4-3-5 glob@4.3.5 444 | 228 verbose lock glob@4.3.5 /home/gajus/.npm/88cb2229-glob-4-3-5.lock 445 | 229 silly lockFile 88cb2229-glob-4-3-5 glob@4.3.5 446 | 230 silly lockFile 88cb2229-glob-4-3-5 glob@4.3.5 447 | 231 silly lockFile 85d3ad16-glob-4-3-5 glob@~4.3.5 448 | 232 silly lockFile 85d3ad16-glob-4-3-5 glob@~4.3.5 449 | 233 http 404 https://registry.npmjs.org/lodash-compat/3.0.0 450 | 234 silly registry.get cb [ 404, 451 | 234 silly registry.get { date: 'Tue, 20 Jan 2015 14:51:04 GMT', 452 | 234 silly registry.get server: 'CouchDB/1.5.0 (Erlang OTP/R16B03)', 453 | 234 silly registry.get 'content-type': 'application/json', 454 | 234 silly registry.get 'cache-control': 'max-age=0', 455 | 234 silly registry.get 'content-length': '52', 456 | 234 silly registry.get 'accept-ranges': 'bytes', 457 | 234 silly registry.get via: '1.1 varnish', 458 | 234 silly registry.get age: '0', 459 | 234 silly registry.get 'x-served-by': 'cache-ams4134-AMS', 460 | 234 silly registry.get 'x-cache': 'MISS', 461 | 234 silly registry.get 'x-cache-hits': '0', 462 | 234 silly registry.get 'x-timer': 'S1421765464.064276,VS0,VE90', 463 | 234 silly registry.get 'keep-alive': 'timeout=10, max=50', 464 | 234 silly registry.get connection: 'Keep-Alive' } ] 465 | 235 silly lockFile 3c21c534-lodash-compat-3-0-0 lodash-compat@3.0.0 466 | 236 silly lockFile 3c21c534-lodash-compat-3-0-0 lodash-compat@3.0.0 467 | 237 verbose about to build /var/www/dev/gajus kuizinas/2014 02 02 gajus.com/_blog/post/lodash-v3/documentation-generator/node_modules/lodash-cli 468 | 238 info /var/www/dev/gajus kuizinas/2014 02 02 gajus.com/_blog/post/lodash-v3/documentation-generator/node_modules/lodash-cli unbuild 469 | 239 info preuninstall lodash-cli@3.0.0 470 | 240 info uninstall lodash-cli@3.0.0 471 | 241 verbose true,/var/www/dev/gajus kuizinas/2014 02 02 gajus.com/_blog/post/lodash-v3/documentation-generator/node_modules,/var/www/dev/gajus kuizinas/2014 02 02 gajus.com/_blog/post/lodash-v3/documentation-generator/node_modules unbuild lodash-cli@3.0.0 472 | 242 verbose /var/www/dev/gajus kuizinas/2014 02 02 gajus.com/_blog/post/lodash-v3/documentation-generator/node_modules/.bin,[object Object] binRoot 473 | 243 info postuninstall lodash-cli@3.0.0 474 | 244 error 404 'lodash-compat' is not in the npm registry. 475 | 244 error 404 You should bug the author to publish it 476 | 244 error 404 477 | 244 error 404 Note that you can also install from a 478 | 244 error 404 tarball, folder, or http url, or git url. 479 | 245 error System Linux 2.6.32-431.11.2.el6.x86_64 480 | 246 error command "node" "/usr/bin/npm" "install" "git+https://github.com/lodash/lodash-cli.git" 481 | 247 error cwd /var/www/dev/gajus kuizinas/2014 02 02 gajus.com/_blog/post/lodash-v3/documentation-generator 482 | 248 error node -v v0.10.26 483 | 249 error npm -v 1.3.6 484 | 250 error code E404 485 | 251 verbose exit [ 1, true ] 486 | -------------------------------------------------------------------------------- /posts/harder-better-faster-stronger-lo-dash-v3/documentation-generator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "documentation-generator", 3 | "description": "Generates documentation for Lo-Dash v3.", 4 | "dependencies": { 5 | "request-promise": "~0.3.3", 6 | "bluebird": "~2.8.1", 7 | "comment-parser": "~0.2.4", 8 | "lodash": "~2.4.1" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /posts/harder-better-faster-stronger-lo-dash-v3/documentation.md: -------------------------------------------------------------------------------- 1 | # Lo-Dash v3 Documentation 2 | 3 | Lo-Dash v3 documentation generated from the source code. 4 | 5 | ## string 6 | 7 | ### camelCase 8 | 9 | https://raw.githubusercontent.com/lodash/lodash/es6/string/camelCase.js 10 | 11 | Converts `string` to camel case. 12 | 13 | See [Wikipedia](http://en.wikipedia.org/wiki/CamelCase) for more details. 14 | 15 | #### Parameters 16 | 17 | | Name | Type | Description | 18 | | --- | --- | --- | 19 | | `string` | `string` | The string to convert.| 20 | 21 | 22 | #### Returns 23 | 24 | | Type | Description | 25 | | --- | --- | 26 | | `string` | the camel cased string.| 27 | 28 | ```js 29 | _.camelCase('Foo Bar'); 30 | // => 'fooBar' 31 | 32 | _.camelCase('--foo-bar'); 33 | // => 'fooBar' 34 | 35 | _.camelCase('__foo_bar__'); 36 | // => 'fooBar' 37 | ``` 38 | 39 | ### capitalize 40 | 41 | https://raw.githubusercontent.com/lodash/lodash/es6/string/capitalize.js 42 | 43 | Capitalizes the first character of `string`. 44 | 45 | #### Parameters 46 | 47 | | Name | Type | Description | 48 | | --- | --- | --- | 49 | | `string` | `string` | The string to capitalize.| 50 | 51 | 52 | #### Returns 53 | 54 | | Type | Description | 55 | | --- | --- | 56 | | `string` | the capitalized string.| 57 | 58 | ```js 59 | _.capitalize('fred'); 60 | // => 'Fred' 61 | ``` 62 | 63 | ### deburr 64 | 65 | https://raw.githubusercontent.com/lodash/lodash/es6/string/deburr.js 66 | 67 | Deburrs `string` by converting latin-1 supplementary letters to basic latin letters. 68 | 69 | See [Wikipedia](http://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table) 70 | 71 | for more details. 72 | 73 | #### Parameters 74 | 75 | | Name | Type | Description | 76 | | --- | --- | --- | 77 | | `string` | `string` | The string to deburr.| 78 | 79 | 80 | #### Returns 81 | 82 | | Type | Description | 83 | | --- | --- | 84 | | `string` | the deburred string.| 85 | 86 | ```js 87 | _.deburr('déjà vu'); 88 | // => 'deja vu' 89 | ``` 90 | 91 | ### endsWith 92 | 93 | https://raw.githubusercontent.com/lodash/lodash/es6/string/endsWith.js 94 | 95 | Checks if `string` ends with the given target string. 96 | 97 | #### Parameters 98 | 99 | | Name | Type | Description | 100 | | --- | --- | --- | 101 | | `string` | `string` | The string to search.| 102 | | `target` | `string` | The string to search for.| 103 | | `position` | `number` | The position to search from.| 104 | 105 | 106 | #### Returns 107 | 108 | | Type | Description | 109 | | --- | --- | 110 | | `boolean` | `true` if `string` ends with `target`, else `false`.| 111 | 112 | ```js 113 | _.endsWith('abc', 'c'); 114 | // => true 115 | 116 | _.endsWith('abc', 'b'); 117 | // => false 118 | 119 | _.endsWith('abc', 'b', 2); 120 | // => true 121 | ``` 122 | 123 | ### escapeRegExp 124 | 125 | https://raw.githubusercontent.com/lodash/lodash/es6/string/escapeRegExp.js 126 | 127 | Escapes the `RegExp` special characters "\", "^", "$", ".", "|", "?", "*", 128 | 129 | "+", "(", ")", "[", "]", "{" and "}" in `string`. 130 | 131 | #### Parameters 132 | 133 | | Name | Type | Description | 134 | | --- | --- | --- | 135 | | `string` | `string` | The string to escape.| 136 | 137 | 138 | #### Returns 139 | 140 | | Type | Description | 141 | | --- | --- | 142 | | `string` | the escaped string.| 143 | 144 | ```js 145 | _.escapeRegExp('[lodash](https://lodash.com/)'); 146 | // => '\[lodash\]\(https://lodash\.com/\)' 147 | ``` 148 | 149 | ### kebabCase 150 | 151 | https://raw.githubusercontent.com/lodash/lodash/es6/string/kebabCase.js 152 | 153 | Converts `string` to kebab case (a.k.a. spinal case). 154 | 155 | See [Wikipedia](http://en.wikipedia.org/wiki/Letter_case#Computers) for 156 | 157 | more details. 158 | 159 | #### Parameters 160 | 161 | | Name | Type | Description | 162 | | --- | --- | --- | 163 | | `string` | `string` | The string to convert.| 164 | 165 | 166 | #### Returns 167 | 168 | | Type | Description | 169 | | --- | --- | 170 | | `string` | the kebab cased string.| 171 | 172 | ```js 173 | _.kebabCase('Foo Bar'); 174 | // => 'foo-bar' 175 | 176 | _.kebabCase('fooBar'); 177 | // => 'foo-bar' 178 | 179 | _.kebabCase('__foo_bar__'); 180 | // => 'foo-bar' 181 | ``` 182 | 183 | ### pad 184 | 185 | https://raw.githubusercontent.com/lodash/lodash/es6/string/pad.js 186 | 187 | Pads `string` on the left and right sides if it is shorter then the given 188 | 189 | padding length. The `chars` string may be truncated if the number of padding 190 | 191 | characters can't be evenly divided by the padding length. 192 | 193 | #### Parameters 194 | 195 | | Name | Type | Description | 196 | | --- | --- | --- | 197 | | `string` | `string` | The string to pad.| 198 | | `length` | `number` | The padding length.| 199 | | `chars` | `string` | The string used as padding.| 200 | 201 | 202 | #### Returns 203 | 204 | | Type | Description | 205 | | --- | --- | 206 | | `string` | the padded string.| 207 | 208 | ```js 209 | _.pad('abc', 8); 210 | // => ' abc ' 211 | 212 | _.pad('abc', 8, '_-'); 213 | // => '_-abc_-_' 214 | 215 | _.pad('abc', 3); 216 | // => 'abc' 217 | ``` 218 | 219 | ### padLeft 220 | 221 | https://raw.githubusercontent.com/lodash/lodash/es6/string/padLeft.js 222 | 223 | Pads `string` on the left side if it is shorter then the given padding 224 | 225 | length. The `chars` string may be truncated if the number of padding 226 | 227 | characters exceeds the padding length. 228 | 229 | #### Parameters 230 | 231 | | Name | Type | Description | 232 | | --- | --- | --- | 233 | | `string` | `string` | The string to pad.| 234 | | `length` | `number` | The padding length.| 235 | | `chars` | `string` | The string used as padding.| 236 | 237 | 238 | #### Returns 239 | 240 | | Type | Description | 241 | | --- | --- | 242 | | `string` | the padded string.| 243 | 244 | ```js 245 | _.padLeft('abc', 6); 246 | // => ' abc' 247 | 248 | _.padLeft('abc', 6, '_-'); 249 | // => '_-_abc' 250 | 251 | _.padLeft('abc', 3); 252 | // => 'abc' 253 | ``` 254 | 255 | ### padRight 256 | 257 | https://raw.githubusercontent.com/lodash/lodash/es6/string/padRight.js 258 | 259 | Pads `string` on the right side if it is shorter then the given padding 260 | 261 | length. The `chars` string may be truncated if the number of padding 262 | 263 | characters exceeds the padding length. 264 | 265 | #### Parameters 266 | 267 | | Name | Type | Description | 268 | | --- | --- | --- | 269 | | `string` | `string` | The string to pad.| 270 | | `length` | `number` | The padding length.| 271 | | `chars` | `string` | The string used as padding.| 272 | 273 | 274 | #### Returns 275 | 276 | | Type | Description | 277 | | --- | --- | 278 | | `string` | the padded string.| 279 | 280 | ```js 281 | _.padRight('abc', 6); 282 | // => 'abc ' 283 | 284 | _.padRight('abc', 6, '_-'); 285 | // => 'abc_-_' 286 | 287 | _.padRight('abc', 3); 288 | // => 'abc' 289 | ``` 290 | 291 | ### repeat 292 | 293 | https://raw.githubusercontent.com/lodash/lodash/es6/string/repeat.js 294 | 295 | Repeats the given string `n` times. 296 | 297 | #### Parameters 298 | 299 | | Name | Type | Description | 300 | | --- | --- | --- | 301 | | `string` | `string` | The string to repeat.| 302 | | `n` | `number` | The number of times to repeat the string.| 303 | 304 | 305 | #### Returns 306 | 307 | | Type | Description | 308 | | --- | --- | 309 | | `string` | the repeated string.| 310 | 311 | ```js 312 | _.repeat('*', 3); 313 | // => '***' 314 | 315 | _.repeat('abc', 2); 316 | // => 'abcabc' 317 | 318 | _.repeat('abc', 0); 319 | // => '' 320 | ``` 321 | 322 | ### snakeCase 323 | 324 | https://raw.githubusercontent.com/lodash/lodash/es6/string/snakeCase.js 325 | 326 | Converts `string` to snake case. 327 | 328 | See [Wikipedia](http://en.wikipedia.org/wiki/Snake_case) for more details. 329 | 330 | #### Parameters 331 | 332 | | Name | Type | Description | 333 | | --- | --- | --- | 334 | | `string` | `string` | The string to convert.| 335 | 336 | 337 | #### Returns 338 | 339 | | Type | Description | 340 | | --- | --- | 341 | | `string` | the snake cased string.| 342 | 343 | ```js 344 | _.snakeCase('Foo Bar'); 345 | // => 'foo_bar' 346 | 347 | _.snakeCase('--foo-bar'); 348 | // => 'foo_bar' 349 | 350 | _.snakeCase('fooBar'); 351 | // => 'foo_bar' 352 | ``` 353 | 354 | ### startsWith 355 | 356 | https://raw.githubusercontent.com/lodash/lodash/es6/string/startsWith.js 357 | 358 | Checks if `string` starts with the given target string. 359 | 360 | #### Parameters 361 | 362 | | Name | Type | Description | 363 | | --- | --- | --- | 364 | | `string` | `string` | The string to search.| 365 | | `target` | `string` | The string to search for.| 366 | | `position` | `number` | The position to search from.| 367 | 368 | 369 | #### Returns 370 | 371 | | Type | Description | 372 | | --- | --- | 373 | | `boolean` | `true` if `string` starts with `target`, else `false`.| 374 | 375 | ```js 376 | _.startsWith('abc', 'a'); 377 | // => true 378 | 379 | _.startsWith('abc', 'b'); 380 | // => false 381 | 382 | _.startsWith('abc', 'b', 1); 383 | // => true 384 | ``` 385 | 386 | ### trim 387 | 388 | https://raw.githubusercontent.com/lodash/lodash/es6/string/trim.js 389 | 390 | Removes leading and trailing whitespace or specified characters from `string`. 391 | 392 | #### Parameters 393 | 394 | | Name | Type | Description | 395 | | --- | --- | --- | 396 | | `string` | `string` | The string to trim.| 397 | | `chars` | `string` | The characters to trim.| 398 | 399 | 400 | #### Returns 401 | 402 | | Type | Description | 403 | | --- | --- | 404 | | `string` | the trimmed string.| 405 | 406 | ```js 407 | _.trim(' abc '); 408 | // => 'abc' 409 | 410 | _.trim('-_-abc-_-', '_-'); 411 | // => 'abc' 412 | 413 | _.map([' foo ', ' bar '], _.trim); 414 | // => ['foo', 'bar] 415 | ``` 416 | 417 | ### trimLeft 418 | 419 | https://raw.githubusercontent.com/lodash/lodash/es6/string/trimLeft.js 420 | 421 | Removes leading whitespace or specified characters from `string`. 422 | 423 | #### Parameters 424 | 425 | | Name | Type | Description | 426 | | --- | --- | --- | 427 | | `string` | `string` | The string to trim.| 428 | | `chars` | `string` | The characters to trim.| 429 | 430 | 431 | #### Returns 432 | 433 | | Type | Description | 434 | | --- | --- | 435 | | `string` | the trimmed string.| 436 | 437 | ```js 438 | _.trimLeft(' abc '); 439 | // => 'abc ' 440 | 441 | _.trimLeft('-_-abc-_-', '_-'); 442 | // => 'abc-_-' 443 | ``` 444 | 445 | ### trimRight 446 | 447 | https://raw.githubusercontent.com/lodash/lodash/es6/string/trimRight.js 448 | 449 | Removes trailing whitespace or specified characters from `string`. 450 | 451 | #### Parameters 452 | 453 | | Name | Type | Description | 454 | | --- | --- | --- | 455 | | `string` | `string` | The string to trim.| 456 | | `chars` | `string` | The characters to trim.| 457 | 458 | 459 | #### Returns 460 | 461 | | Type | Description | 462 | | --- | --- | 463 | | `string` | the trimmed string.| 464 | 465 | ```js 466 | _.trimRight(' abc '); 467 | // => ' abc' 468 | 469 | _.trimRight('-_-abc-_-', '_-'); 470 | // => '-_-abc' 471 | ``` 472 | 473 | ### trunc 474 | 475 | https://raw.githubusercontent.com/lodash/lodash/es6/string/trunc.js 476 | 477 | Truncates `string` if it is longer than the given maximum string length. 478 | 479 | The last characters of the truncated string are replaced with the omission 480 | 481 | string which defaults to "...". 482 | 483 | #### Parameters 484 | 485 | | Name | Type | Description | 486 | | --- | --- | --- | 487 | | `string` | `string` | The string to truncate.| 488 | | `options` | `Object|number` | The options object or maximum string length.| 489 | | `options.length` | `number` | The maximum string length.| 490 | | `options.omission` | `string` | The string to indicate text is omitted.| 491 | | `options.separator` | `RegExp|string` | The separator pattern to truncate to.| 492 | 493 | 494 | #### Returns 495 | 496 | | Type | Description | 497 | | --- | --- | 498 | | `string` | the truncated string.| 499 | 500 | ```js 501 | _.trunc('hi-diddly-ho there, neighborino'); 502 | // => 'hi-diddly-ho there, neighbo...' 503 | 504 | _.trunc('hi-diddly-ho there, neighborino', 24); 505 | // => 'hi-diddly-ho there, n...' 506 | 507 | _.trunc('hi-diddly-ho there, neighborino', { 'length': 24, 'separator': ' ' }); 508 | // => 'hi-diddly-ho there,...' 509 | 510 | _.trunc('hi-diddly-ho there, neighborino', { 'length': 24, 'separator': /,? +/ }); 511 | //=> 'hi-diddly-ho there...' 512 | 513 | _.trunc('hi-diddly-ho there, neighborino', { 'omission': ' [...]' }); 514 | // => 'hi-diddly-ho there, neig [...]' 515 | ``` 516 | 517 | ### words 518 | 519 | https://raw.githubusercontent.com/lodash/lodash/es6/string/words.js 520 | 521 | Splits `string` into an array of its words. 522 | 523 | #### Parameters 524 | 525 | | Name | Type | Description | 526 | | --- | --- | --- | 527 | | `string` | `string` | The string to inspect.| 528 | | `pattern` | `RegExp|string` | The pattern to match words.| 529 | 530 | 531 | #### Returns 532 | 533 | | Type | Description | 534 | | --- | --- | 535 | | `Array` | the words of `string`.| 536 | 537 | ```js 538 | _.words('fred, barney, & pebbles'); 539 | // => ['fred', 'barney', 'pebbles'] 540 | 541 | _.words('fred, barney, & pebbles', /[^, ]+/g); 542 | // => ['fred', 'barney', '&', 'pebbles'] 543 | ``` 544 | 545 | ## array 546 | 547 | ### chunk 548 | 549 | https://raw.githubusercontent.com/lodash/lodash/es6/array/chunk.js 550 | 551 | Creates an array of elements split into groups the length of `size`. 552 | 553 | If `collection` can't be split evenly, the final chunk will be the remaining 554 | 555 | elements. 556 | 557 | #### Parameters 558 | 559 | | Name | Type | Description | 560 | | --- | --- | --- | 561 | | `array` | `Array` | The array to process.| 562 | | `size` | `numer` | The length of each chunk.| 563 | 564 | 565 | #### Returns 566 | 567 | | Type | Description | 568 | | --- | --- | 569 | | `Array` | the new array containing chunks.| 570 | 571 | ```js 572 | _.chunk(['a', 'b', 'c', 'd'], 2); 573 | // => [['a', 'b'], ['c', 'd']] 574 | 575 | _.chunk(['a', 'b', 'c', 'd'], 3); 576 | // => [['a', 'b', 'c'], ['d']] 577 | ``` 578 | 579 | ### dropRight 580 | 581 | https://raw.githubusercontent.com/lodash/lodash/es6/array/dropRight.js 582 | 583 | Creates a slice of `array` with `n` elements dropped from the end. 584 | 585 | #### Parameters 586 | 587 | | Name | Type | Description | 588 | | --- | --- | --- | 589 | | `array` | `Array` | The array to query.| 590 | | `n` | `number` | The number of elements to drop.| 591 | 592 | 593 | #### Returns 594 | 595 | | Type | Description | 596 | | --- | --- | 597 | | `Array` | the slice of `array`.| 598 | 599 | ```js 600 | _.dropRight([1, 2, 3]); 601 | // => [1, 2] 602 | 603 | _.dropRight([1, 2, 3], 2); 604 | // => [1] 605 | 606 | _.dropRight([1, 2, 3], 5); 607 | // => [] 608 | 609 | _.dropRight([1, 2, 3], 0); 610 | // => [1, 2, 3] 611 | ``` 612 | 613 | ### dropRightWhile 614 | 615 | https://raw.githubusercontent.com/lodash/lodash/es6/array/dropRightWhile.js 616 | 617 | Creates a slice of `array` excluding elements dropped from the end. 618 | 619 | Elements are dropped until `predicate` returns falsey. The predicate is 620 | 621 | bound to `thisArg` and invoked with three arguments; (value, index, array). 622 | 623 | 624 | 625 | If a property name is provided for `predicate` the created "_.property" 626 | 627 | style callback returns the property value of the given element. 628 | 629 | 630 | 631 | If an object is provided for `predicate` the created "_.matches" style 632 | 633 | callback returns `true` for elements that have the properties of the given 634 | 635 | object, else `false`. 636 | 637 | #### Parameters 638 | 639 | | Name | Type | Description | 640 | | --- | --- | --- | 641 | | `array` | `Array` | The array to query.| 642 | | `predicate` | `Function|Object|string` | The function invoked 643 | per element.| 644 | | `thisArg` | `*` | The `this` binding of `predicate`.| 645 | 646 | 647 | #### Returns 648 | 649 | | Type | Description | 650 | | --- | --- | 651 | | `Array` | the slice of `array`.| 652 | 653 | ```js 654 | _.dropRightWhile([1, 2, 3], function(n) { return n > 1; }); 655 | // => [1] 656 | 657 | var users = [ 658 | { 'user': 'barney', 'status': 'busy', 'active': false }, 659 | { 'user': 'fred', 'status': 'busy', 'active': true }, 660 | { 'user': 'pebbles', 'status': 'away', 'active': true } 661 | ]; 662 | 663 | // using the "_.property" callback shorthand 664 | _.pluck(_.dropRightWhile(users, 'active'), 'user'); 665 | // => ['barney'] 666 | 667 | // using the "_.matches" callback shorthand 668 | _.pluck(_.dropRightWhile(users, { 'status': 'away' }), 'user'); 669 | // => ['barney', 'fred'] 670 | ``` 671 | 672 | ### dropWhile 673 | 674 | https://raw.githubusercontent.com/lodash/lodash/es6/array/dropWhile.js 675 | 676 | Creates a slice of `array` excluding elements dropped from the beginning. 677 | 678 | Elements are dropped until `predicate` returns falsey. The predicate is 679 | 680 | bound to `thisArg` and invoked with three arguments; (value, index, array). 681 | 682 | 683 | 684 | If a property name is provided for `predicate` the created "_.property" 685 | 686 | style callback returns the property value of the given element. 687 | 688 | 689 | 690 | If an object is provided for `predicate` the created "_.matches" style 691 | 692 | callback returns `true` for elements that have the properties of the given 693 | 694 | object, else `false`. 695 | 696 | #### Parameters 697 | 698 | | Name | Type | Description | 699 | | --- | --- | --- | 700 | | `array` | `Array` | The array to query.| 701 | | `predicate` | `Function|Object|string` | The function invoked 702 | per element.| 703 | | `thisArg` | `*` | The `this` binding of `predicate`.| 704 | 705 | 706 | #### Returns 707 | 708 | | Type | Description | 709 | | --- | --- | 710 | | `Array` | the slice of `array`.| 711 | 712 | ```js 713 | _.dropWhile([1, 2, 3], function(n) { return n < 3; }); 714 | // => [3] 715 | 716 | var users = [ 717 | { 'user': 'barney', 'status': 'busy', 'active': true }, 718 | { 'user': 'fred', 'status': 'busy', 'active': false }, 719 | { 'user': 'pebbles', 'status': 'away', 'active': true } 720 | ]; 721 | 722 | // using the "_.property" callback shorthand 723 | _.pluck(_.dropWhile(users, 'active'), 'user'); 724 | // => ['fred', 'pebbles'] 725 | 726 | // using the "_.matches" callback shorthand 727 | _.pluck(_.dropWhile(users, { 'status': 'busy' }), 'user'); 728 | // => ['pebbles'] 729 | ``` 730 | 731 | ### flattenDeep 732 | 733 | https://raw.githubusercontent.com/lodash/lodash/es6/array/flattenDeep.js 734 | 735 | Recursively flattens a nested array. 736 | 737 | #### Parameters 738 | 739 | | Name | Type | Description | 740 | | --- | --- | --- | 741 | | `array` | `Array` | The array to recursively flatten.| 742 | 743 | 744 | #### Returns 745 | 746 | | Type | Description | 747 | | --- | --- | 748 | | `Array` | the new flattened array.| 749 | 750 | ```js 751 | _.flattenDeep([1, [2], [3, [[4]]]]); 752 | // => [1, 2, 3, 4]; 753 | ``` 754 | 755 | ### pullAt 756 | 757 | https://raw.githubusercontent.com/lodash/lodash/es6/array/pullAt.js 758 | 759 | Removes elements from `array` corresponding to the given indexes and returns 760 | 761 | an array of the removed elements. Indexes may be specified as an array of 762 | 763 | indexes or as individual arguments. 764 | 765 | 766 | 767 | **Note:** Unlike `_.at`, this method mutates `array`. 768 | 769 | #### Parameters 770 | 771 | | Name | Type | Description | 772 | | --- | --- | --- | 773 | | `array` | `Array` | The array to modify.| 774 | | `indexes` | `...(number|number[])` | The indexes of elements to remove, 775 | specified as individual indexes or arrays of indexes.| 776 | 777 | 778 | #### Returns 779 | 780 | | Type | Description | 781 | | --- | --- | 782 | | `Array` | the new array of removed elements.| 783 | 784 | ```js 785 | var array = [5, 10, 15, 20]; 786 | var evens = _.pullAt(array, [1, 3]); 787 | 788 | console.log(array); 789 | // => [5, 15] 790 | 791 | console.log(evens); 792 | // => [10, 20] 793 | ``` 794 | 795 | ### slice 796 | 797 | https://raw.githubusercontent.com/lodash/lodash/es6/array/slice.js 798 | 799 | Creates a slice of `array` from `start` up to, but not including, `end`. 800 | 801 | 802 | 803 | **Note:** This function is used instead of `Array#slice` to support node 804 | 805 | lists in IE < 9 and to ensure dense arrays are returned. 806 | 807 | #### Parameters 808 | 809 | | Name | Type | Description | 810 | | --- | --- | --- | 811 | | `array` | `Array` | The array to slice.| 812 | | `start` | `number` | The start position.| 813 | | `end` | `number` | The end position.| 814 | 815 | 816 | #### Returns 817 | 818 | | Type | Description | 819 | | --- | --- | 820 | | `Array` | the slice of `array`.| 821 | 822 | 823 | ### sortedLastIndex 824 | 825 | https://raw.githubusercontent.com/lodash/lodash/es6/array/sortedLastIndex.js 826 | 827 | This method is like `_.sortedIndex` except that it returns the highest 828 | 829 | index at which `value` should be inserted into `array` in order to 830 | 831 | maintain its sort order. 832 | 833 | #### Parameters 834 | 835 | | Name | Type | Description | 836 | | --- | --- | --- | 837 | | `array` | `Array` | The sorted array to inspect.| 838 | | `value` | `*` | The value to evaluate.| 839 | | `iteratee` | `Function|Object|string` | The function invoked 840 | per iteration. If a property name or object is provided it is used to 841 | create a "_.property" or "_.matches" style callback respectively.| 842 | | `thisArg` | `*` | The `this` binding of `iteratee`.| 843 | 844 | 845 | #### Returns 846 | 847 | | Type | Description | 848 | | --- | --- | 849 | | `number` | the index at which `value` should be inserted 850 | into `array`.| 851 | 852 | ```js 853 | _.sortedLastIndex([4, 4, 5, 5, 6, 6], 5); 854 | // => 4 855 | ``` 856 | 857 | ### takeRight 858 | 859 | https://raw.githubusercontent.com/lodash/lodash/es6/array/takeRight.js 860 | 861 | Creates a slice of `array` with `n` elements taken from the end. 862 | 863 | #### Parameters 864 | 865 | | Name | Type | Description | 866 | | --- | --- | --- | 867 | | `array` | `Array` | The array to query.| 868 | | `n` | `number` | The number of elements to take.| 869 | 870 | 871 | #### Returns 872 | 873 | | Type | Description | 874 | | --- | --- | 875 | | `Array` | the slice of `array`.| 876 | 877 | ```js 878 | _.takeRight([1, 2, 3]); 879 | // => [3] 880 | 881 | _.takeRight([1, 2, 3], 2); 882 | // => [2, 3] 883 | 884 | _.takeRight([1, 2, 3], 5); 885 | // => [1, 2, 3] 886 | 887 | _.takeRight([1, 2, 3], 0); 888 | // => [] 889 | ``` 890 | 891 | ### takeRightWhile 892 | 893 | https://raw.githubusercontent.com/lodash/lodash/es6/array/takeRightWhile.js 894 | 895 | Creates a slice of `array` with elements taken from the end. Elements are 896 | 897 | taken until `predicate` returns falsey. The predicate is bound to `thisArg` 898 | 899 | and invoked with three arguments; (value, index, array). 900 | 901 | 902 | 903 | If a property name is provided for `predicate` the created "_.property" 904 | 905 | style callback returns the property value of the given element. 906 | 907 | 908 | 909 | If an object is provided for `predicate` the created "_.matches" style 910 | 911 | callback returns `true` for elements that have the properties of the given 912 | 913 | object, else `false`. 914 | 915 | #### Parameters 916 | 917 | | Name | Type | Description | 918 | | --- | --- | --- | 919 | | `array` | `Array` | The array to query.| 920 | | `predicate` | `Function|Object|string` | The function invoked 921 | per element.| 922 | | `thisArg` | `*` | The `this` binding of `predicate`.| 923 | 924 | 925 | #### Returns 926 | 927 | | Type | Description | 928 | | --- | --- | 929 | | `Array` | the slice of `array`.| 930 | 931 | ```js 932 | _.takeRightWhile([1, 2, 3], function(n) { return n > 1; }); 933 | // => [2, 3] 934 | 935 | var users = [ 936 | { 'user': 'barney', 'status': 'busy', 'active': false }, 937 | { 'user': 'fred', 'status': 'busy', 'active': true }, 938 | { 'user': 'pebbles', 'status': 'away', 'active': true } 939 | ]; 940 | 941 | // using the "_.property" callback shorthand 942 | _.pluck(_.takeRightWhile(users, 'active'), 'user'); 943 | // => ['fred', 'pebbles'] 944 | 945 | // using the "_.matches" callback shorthand 946 | _.pluck(_.takeRightWhile(users, { 'status': 'away' }), 'user'); 947 | // => ['pebbles'] 948 | ``` 949 | 950 | ### takeWhile 951 | 952 | https://raw.githubusercontent.com/lodash/lodash/es6/array/takeWhile.js 953 | 954 | Creates a slice of `array` with elements taken from the beginning. Elements 955 | 956 | are taken until `predicate` returns falsey. The predicate is bound to 957 | 958 | `thisArg` and invoked with three arguments; (value, index, array). 959 | 960 | 961 | 962 | If a property name is provided for `predicate` the created "_.property" 963 | 964 | style callback returns the property value of the given element. 965 | 966 | 967 | 968 | If an object is provided for `predicate` the created "_.matches" style 969 | 970 | callback returns `true` for elements that have the properties of the given 971 | 972 | object, else `false`. 973 | 974 | #### Parameters 975 | 976 | | Name | Type | Description | 977 | | --- | --- | --- | 978 | | `array` | `Array` | The array to query.| 979 | | `predicate` | `Function|Object|string` | The function invoked 980 | per element.| 981 | | `thisArg` | `*` | The `this` binding of `predicate`.| 982 | 983 | 984 | #### Returns 985 | 986 | | Type | Description | 987 | | --- | --- | 988 | | `Array` | the slice of `array`.| 989 | 990 | ```js 991 | _.takeWhile([1, 2, 3], function(n) { return n < 3; }); 992 | // => [1, 2] 993 | 994 | var users = [ 995 | { 'user': 'barney', 'status': 'busy', 'active': true }, 996 | { 'user': 'fred', 'status': 'busy', 'active': false }, 997 | { 'user': 'pebbles', 'status': 'away', 'active': true } 998 | ]; 999 | 1000 | // using the "_.property" callback shorthand 1001 | _.pluck(_.takeWhile(users, 'active'), 'user'); 1002 | // => ['barney'] 1003 | 1004 | // using the "_.matches" callback shorthand 1005 | _.pluck(_.takeWhile(users, { 'status': 'busy' }), 'user'); 1006 | // => ['barney', 'fred'] 1007 | ``` 1008 | 1009 | ## function 1010 | 1011 | ### ary 1012 | 1013 | https://raw.githubusercontent.com/lodash/lodash/es6/function/ary.js 1014 | 1015 | Creates a function that accepts up to `n` arguments ignoring any 1016 | 1017 | additional arguments. 1018 | 1019 | #### Parameters 1020 | 1021 | | Name | Type | Description | 1022 | | --- | --- | --- | 1023 | | `func` | `Function` | The function to cap arguments for.| 1024 | | `n` | `number` | The arity cap.| 1025 | 1026 | 1027 | #### Returns 1028 | 1029 | | Type | Description | 1030 | | --- | --- | 1031 | | `Function` | the new function.| 1032 | 1033 | ```js 1034 | _.map(['6', '8', '10'], _.ary(parseInt, 1)); 1035 | // => [6, 8, 10] 1036 | ``` 1037 | 1038 | ### before 1039 | 1040 | https://raw.githubusercontent.com/lodash/lodash/es6/function/before.js 1041 | 1042 | Creates a function that invokes `func`, with the `this` binding and arguments 1043 | 1044 | of the created function, while it is called less than `n` times. Subsequent 1045 | 1046 | calls to the created function return the result of the last `func` invocation. 1047 | 1048 | #### Parameters 1049 | 1050 | | Name | Type | Description | 1051 | | --- | --- | --- | 1052 | | `n` | `number` | The number of calls at which `func` is no longer invoked.| 1053 | | `func` | `Function` | The function to restrict.| 1054 | 1055 | 1056 | #### Returns 1057 | 1058 | | Type | Description | 1059 | | --- | --- | 1060 | | `Function` | the new restricted function.| 1061 | 1062 | ```js 1063 | jQuery('#add').on('click', _.before(5, addContactToList)); 1064 | // => allows adding up to 4 contacts to the list 1065 | ``` 1066 | 1067 | ### curryRight 1068 | 1069 | https://raw.githubusercontent.com/lodash/lodash/es6/function/curryRight.js 1070 | 1071 | This method is like `_.curry` except that arguments are applied to `func` 1072 | 1073 | in the manner of `_.partialRight` instead of `_.partial`. 1074 | 1075 | 1076 | 1077 | The `_.curryRight.placeholder` value, which defaults to `_` in monolithic 1078 | 1079 | builds, may be used as a placeholder for provided arguments. 1080 | 1081 | 1082 | 1083 | **Note:** This method does not set the `length` property of curried functions. 1084 | 1085 | #### Parameters 1086 | 1087 | | Name | Type | Description | 1088 | | --- | --- | --- | 1089 | | `func` | `Function` | The function to curry.| 1090 | | `arity` | `number` | The arity of `func`.| 1091 | 1092 | 1093 | #### Returns 1094 | 1095 | | Type | Description | 1096 | | --- | --- | 1097 | | `Function` | the new curried function.| 1098 | 1099 | ```js 1100 | var abc = function(a, b, c) { 1101 | return [a, b, c]; 1102 | }; 1103 | 1104 | var curried = _.curryRight(abc); 1105 | 1106 | curried(3)(2)(1); 1107 | // => [1, 2, 3] 1108 | 1109 | curried(2, 3)(1); 1110 | // => [1, 2, 3] 1111 | 1112 | curried(1, 2, 3); 1113 | // => [1, 2, 3] 1114 | 1115 | // using placeholders 1116 | curried(3)(1, _)(2); 1117 | // => [1, 2, 3] 1118 | ``` 1119 | 1120 | ### flow 1121 | 1122 | https://raw.githubusercontent.com/lodash/lodash/es6/function/flow.js 1123 | 1124 | Creates a function that returns the result of invoking the provided 1125 | 1126 | functions with the `this` binding of the created function, where each 1127 | 1128 | successive invocation is supplied the return value of the previous. 1129 | 1130 | #### Parameters 1131 | 1132 | | Name | Type | Description | 1133 | | --- | --- | --- | 1134 | | `funcs` | `...Function` | Functions to invoke.| 1135 | 1136 | 1137 | #### Returns 1138 | 1139 | | Type | Description | 1140 | | --- | --- | 1141 | | `Function` | the new function.| 1142 | 1143 | ```js 1144 | function add(x, y) { 1145 | return x + y; 1146 | } 1147 | 1148 | function square(n) { 1149 | return n * n; 1150 | } 1151 | 1152 | var addSquare = _.flow(add, square); 1153 | addSquare(1, 2); 1154 | // => 9 1155 | ``` 1156 | 1157 | ### negate 1158 | 1159 | https://raw.githubusercontent.com/lodash/lodash/es6/function/negate.js 1160 | 1161 | Creates a function that negates the result of the predicate `func`. The 1162 | 1163 | `func` predicate is invoked with the `this` binding and arguments of the 1164 | 1165 | created function. 1166 | 1167 | #### Parameters 1168 | 1169 | | Name | Type | Description | 1170 | | --- | --- | --- | 1171 | | `predicate` | `Function` | The predicate to negate.| 1172 | 1173 | 1174 | #### Returns 1175 | 1176 | | Type | Description | 1177 | | --- | --- | 1178 | | `Function` | the new function.| 1179 | 1180 | ```js 1181 | function isEven(n) { 1182 | return n % 2 == 0; 1183 | } 1184 | 1185 | _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven)); 1186 | // => [1, 3, 5] 1187 | ``` 1188 | 1189 | ### rearg 1190 | 1191 | https://raw.githubusercontent.com/lodash/lodash/es6/function/rearg.js 1192 | 1193 | Creates a function that invokes `func` with arguments arranged according 1194 | 1195 | to the specified indexes where the argument value at the first index is 1196 | 1197 | provided as the first argument, the argument value at the second index is 1198 | 1199 | provided as the second argument, and so on. 1200 | 1201 | #### Parameters 1202 | 1203 | | Name | Type | Description | 1204 | | --- | --- | --- | 1205 | | `func` | `Function` | The function to rearrange arguments for.| 1206 | | `indexes` | `...(number|number[])` | The arranged argument indexes, 1207 | specified as individual indexes or arrays of indexes.| 1208 | 1209 | 1210 | #### Returns 1211 | 1212 | | Type | Description | 1213 | | --- | --- | 1214 | | `Function` | the new function.| 1215 | 1216 | ```js 1217 | var rearged = _.rearg(function(a, b, c) { 1218 | return [a, b, c]; 1219 | }, 2, 0, 1); 1220 | 1221 | rearged('b', 'c', 'a') 1222 | // => ['a', 'b', 'c'] 1223 | 1224 | var map = _.rearg(_.map, [1, 0]); 1225 | map(function(n) { return n * 3; }, [1, 2, 3]); 1226 | // => [3, 6, 9] 1227 | ``` 1228 | 1229 | ## lang 1230 | 1231 | ### isError 1232 | 1233 | https://raw.githubusercontent.com/lodash/lodash/es6/lang/isError.js 1234 | 1235 | Checks if `value` is an `Error`, `EvalError`, `RangeError`, `ReferenceError`, 1236 | 1237 | `SyntaxError`, `TypeError`, or `URIError` object. 1238 | 1239 | #### Parameters 1240 | 1241 | | Name | Type | Description | 1242 | | --- | --- | --- | 1243 | | `value` | `*` | The value to check.| 1244 | 1245 | 1246 | #### Returns 1247 | 1248 | | Type | Description | 1249 | | --- | --- | 1250 | | `boolean` | `true` if `value` is an error object, else `false`.| 1251 | 1252 | ```js 1253 | _.isError(new Error); 1254 | // => true 1255 | 1256 | _.isError(Error); 1257 | // => false 1258 | ``` 1259 | 1260 | ### isMatch 1261 | 1262 | https://raw.githubusercontent.com/lodash/lodash/es6/lang/isMatch.js 1263 | 1264 | Performs a deep comparison between `object` and `source` to determine if 1265 | 1266 | `object` contains equivalent property values. If `customizer` is provided 1267 | 1268 | it is invoked to compare values. If `customizer` returns `undefined` 1269 | 1270 | comparisons are handled by the method instead. The `customizer` is bound 1271 | 1272 | to `thisArg` and invoked with three arguments; (value, other, index|key). 1273 | 1274 | 1275 | 1276 | **Note:** This method supports comparing properties of arrays, booleans, 1277 | 1278 | `Date` objects, numbers, `Object` objects, regexes, and strings. Functions 1279 | 1280 | and DOM nodes are **not** supported. Provide a customizer function to extend 1281 | 1282 | support for comparing other values. 1283 | 1284 | #### Parameters 1285 | 1286 | | Name | Type | Description | 1287 | | --- | --- | --- | 1288 | | `source` | `Object` | The object to inspect.| 1289 | | `source` | `Object` | The object of property values to match.| 1290 | | `customizer` | `Function` | The function to customize comparing values.| 1291 | | `thisArg` | `*` | The `this` binding of `customizer`.| 1292 | 1293 | 1294 | #### Returns 1295 | 1296 | | Type | Description | 1297 | | --- | --- | 1298 | | `boolean` | `true` if `object` is a match, else `false`.| 1299 | 1300 | ```js 1301 | var object = { 'user': 'fred', 'age': 40 }; 1302 | 1303 | _.isMatch(object, { 'age': 40 }); 1304 | // => true 1305 | 1306 | _.isMatch(object, { 'age': 36 }); 1307 | // => false 1308 | 1309 | // using a customizer callback 1310 | var object = { 'greeting': 'hello' }; 1311 | var source = { 'greeting': 'hi' }; 1312 | 1313 | _.isMatch(object, source, function(value, other) { 1314 | return _.every([value, other], RegExp.prototype.test, /^h(?:i|ello)$/) || undefined; 1315 | }); 1316 | // => true 1317 | ``` 1318 | 1319 | ### isNative 1320 | 1321 | https://raw.githubusercontent.com/lodash/lodash/es6/lang/isNative.js 1322 | 1323 | Checks if `value` is a native function. 1324 | 1325 | #### Parameters 1326 | 1327 | | Name | Type | Description | 1328 | | --- | --- | --- | 1329 | | `value` | `*` | The value to check.| 1330 | 1331 | 1332 | #### Returns 1333 | 1334 | | Type | Description | 1335 | | --- | --- | 1336 | | `boolean` | `true` if `value` is a native function, else `false`.| 1337 | 1338 | ```js 1339 | _.isNative(Array.prototype.push); 1340 | // => true 1341 | 1342 | _.isNative(_); 1343 | // => false 1344 | ``` 1345 | 1346 | ### isTypedArray 1347 | 1348 | https://raw.githubusercontent.com/lodash/lodash/es6/lang/isTypedArray.js 1349 | 1350 | Checks if `value` is classified as a typed array. 1351 | 1352 | #### Parameters 1353 | 1354 | | Name | Type | Description | 1355 | | --- | --- | --- | 1356 | | `value` | `*` | The value to check.| 1357 | 1358 | 1359 | #### Returns 1360 | 1361 | | Type | Description | 1362 | | --- | --- | 1363 | | `boolean` | `true` if `value` is correctly classified, else `false`.| 1364 | 1365 | ```js 1366 | _.isTypedArray(new Uint8Array); 1367 | // => true 1368 | 1369 | _.isTypedArray([]); 1370 | // => false 1371 | ``` 1372 | 1373 | ### toPlainObject 1374 | 1375 | https://raw.githubusercontent.com/lodash/lodash/es6/lang/toPlainObject.js 1376 | 1377 | Converts `value` to a plain object flattening inherited enumerable 1378 | 1379 | properties of `value` to own properties of the plain object. 1380 | 1381 | #### Parameters 1382 | 1383 | | Name | Type | Description | 1384 | | --- | --- | --- | 1385 | | `value` | `*` | The value to convert.| 1386 | 1387 | 1388 | #### Returns 1389 | 1390 | | Type | Description | 1391 | | --- | --- | 1392 | | `Object` | the converted plain object.| 1393 | 1394 | ```js 1395 | function Foo() { 1396 | this.b = 2; 1397 | } 1398 | 1399 | Foo.prototype.c = 3; 1400 | 1401 | _.assign({ 'a': 1 }, new Foo); 1402 | // => { 'a': 1, 'b': 2 } 1403 | 1404 | _.assign({ 'a': 1 }, _.toPlainObject(new Foo)); 1405 | // => { 'a': 1, 'b': 2, 'c': 3 } 1406 | ``` 1407 | 1408 | ## utility 1409 | 1410 | ### attempt 1411 | 1412 | https://raw.githubusercontent.com/lodash/lodash/es6/utility/attempt.js 1413 | 1414 | Attempts to invoke `func`, returning either the result or the caught 1415 | 1416 | error object. 1417 | 1418 | #### Parameters 1419 | 1420 | | Name | Type | Description | 1421 | | --- | --- | --- | 1422 | | `func` | `*` | The function to attempt.| 1423 | 1424 | 1425 | #### Returns 1426 | 1427 | | Type | Description | 1428 | | --- | --- | 1429 | | `*` | the `func` result or error object.| 1430 | 1431 | ```js 1432 | // avoid throwing errors for invalid selectors 1433 | var elements = _.attempt(function() { 1434 | return document.querySelectorAll(selector); 1435 | }); 1436 | 1437 | if (_.isError(elements)) { 1438 | elements = []; 1439 | } 1440 | ``` 1441 | 1442 | ### matches 1443 | 1444 | https://raw.githubusercontent.com/lodash/lodash/es6/utility/matches.js 1445 | 1446 | Creates a function which performs a deep comparison between a given object 1447 | 1448 | and `source`, returning `true` if the given object has equivalent property 1449 | 1450 | values, else `false`. 1451 | 1452 | #### Parameters 1453 | 1454 | | Name | Type | Description | 1455 | | --- | --- | --- | 1456 | | `source` | `Object` | The object of property values to match.| 1457 | 1458 | 1459 | #### Returns 1460 | 1461 | | Type | Description | 1462 | | --- | --- | 1463 | | `Function` | the new function.| 1464 | 1465 | ```js 1466 | var users = [ 1467 | { 'user': 'fred', 'age': 40 }, 1468 | { 'user': 'barney', 'age': 36 } 1469 | ]; 1470 | 1471 | var matchesAge = _.matches({ 'age': 36 }); 1472 | 1473 | _.filter(users, matchesAge); 1474 | // => [{ 'user': 'barney', 'age': 36 }] 1475 | 1476 | _.find(users, matchesAge); 1477 | // => { 'user': 'barney', 'age': 36 } 1478 | ``` 1479 | 1480 | ### propertyOf 1481 | 1482 | https://raw.githubusercontent.com/lodash/lodash/es6/utility/propertyOf.js 1483 | 1484 | The inverse of `_.property`; this method creates a function which returns 1485 | 1486 | the property value of a given key on `object`. 1487 | 1488 | #### Parameters 1489 | 1490 | | Name | Type | Description | 1491 | | --- | --- | --- | 1492 | | `object` | `Object` | The object to inspect.| 1493 | 1494 | 1495 | #### Returns 1496 | 1497 | | Type | Description | 1498 | | --- | --- | 1499 | | `Function` | the new function.| 1500 | 1501 | ```js 1502 | var object = { 'user': 'fred', 'age': 40, 'active': true }; 1503 | _.map(['active', 'user'], _.propertyOf(object)); 1504 | // => [true, 'fred'] 1505 | 1506 | var object = { 'a': 3, 'b': 1, 'c': 2 }; 1507 | _.sortBy(['a', 'b', 'c'], _.propertyOf(object)); 1508 | // => ['b', 'c', 'a'] 1509 | ``` 1510 | 1511 | ## collection 1512 | 1513 | ### partition 1514 | 1515 | https://raw.githubusercontent.com/lodash/lodash/es6/collection/partition.js 1516 | 1517 | Creates an array of elements split into two groups, the first of which 1518 | 1519 | contains elements `predicate` returns truthy for, while the second of which 1520 | 1521 | contains elements `predicate` returns falsey for. The predicate is bound 1522 | 1523 | to `thisArg` and invoked with three arguments; (value, index|key, collection). 1524 | 1525 | 1526 | 1527 | If a property name is provided for `predicate` the created "_.property" 1528 | 1529 | style callback returns the property value of the given element. 1530 | 1531 | 1532 | 1533 | If an object is provided for `predicate` the created "_.matches" style 1534 | 1535 | callback returns `true` for elements that have the properties of the given 1536 | 1537 | object, else `false`. 1538 | 1539 | #### Parameters 1540 | 1541 | | Name | Type | Description | 1542 | | --- | --- | --- | 1543 | | `collection` | `Array|Object|string` | The collection to iterate over.| 1544 | | `predicate` | `Function|Object|string` | The function invoked 1545 | per iteration. If a property name or object is provided it is used to 1546 | create a "_.property" or "_.matches" style callback respectively.| 1547 | | `thisArg` | `*` | The `this` binding of `predicate`.| 1548 | 1549 | 1550 | #### Returns 1551 | 1552 | | Type | Description | 1553 | | --- | --- | 1554 | | `Array` | the array of grouped elements.| 1555 | 1556 | ```js 1557 | _.partition([1, 2, 3], function(n) { return n % 2; }); 1558 | // => [[1, 3], [2]] 1559 | 1560 | _.partition([1.2, 2.3, 3.4], function(n) { return this.floor(n) % 2; }, Math); 1561 | // => [[1, 3], [2]] 1562 | 1563 | var users = [ 1564 | { 'user': 'barney', 'age': 36, 'active': false }, 1565 | { 'user': 'fred', 'age': 40, 'active': true }, 1566 | { 'user': 'pebbles', 'age': 1, 'active': false } 1567 | ]; 1568 | 1569 | // using the "_.matches" callback shorthand 1570 | _.map(_.partition(users, { 'age': 1 }), function(array) { return _.pluck(array, 'user'); }); 1571 | // => [['pebbles'], ['barney', 'fred']] 1572 | 1573 | // using the "_.property" callback shorthand 1574 | _.map(_.partition(users, 'active'), function(array) { return _.pluck(array, 'user'); }); 1575 | // => [['fred'], ['barney', 'pebbles']] 1576 | ``` 1577 | 1578 | ### sortByAll 1579 | 1580 | https://raw.githubusercontent.com/lodash/lodash/es6/collection/sortByAll.js 1581 | 1582 | This method is like `_.sortBy` except that it sorts by property names 1583 | 1584 | instead of an iteratee function. 1585 | 1586 | #### Parameters 1587 | 1588 | | Name | Type | Description | 1589 | | --- | --- | --- | 1590 | | `collection` | `Array|Object|string` | The collection to iterate over.| 1591 | | `props` | `...(string|string[])` | The property names to sort by, 1592 | specified as individual property names or arrays of property names.| 1593 | 1594 | 1595 | #### Returns 1596 | 1597 | | Type | Description | 1598 | | --- | --- | 1599 | | `Array` | the new sorted array.| 1600 | 1601 | ```js 1602 | var users = [ 1603 | { 'user': 'barney', 'age': 36 }, 1604 | { 'user': 'fred', 'age': 40 }, 1605 | { 'user': 'barney', 'age': 26 }, 1606 | { 'user': 'fred', 'age': 30 } 1607 | ]; 1608 | 1609 | _.map(_.sortByAll(users, ['user', 'age']), _.values); 1610 | // => [['barney', 26], ['barney', 36], ['fred', 30], ['fred', 40]] 1611 | ``` 1612 | 1613 | ## object 1614 | 1615 | ### keysIn 1616 | 1617 | https://raw.githubusercontent.com/lodash/lodash/es6/object/keysIn.js 1618 | 1619 | Creates an array of the own and inherited enumerable property names of `object`. 1620 | 1621 | 1622 | 1623 | **Note:** Non-object values are coerced to objects. 1624 | 1625 | #### Parameters 1626 | 1627 | | Name | Type | Description | 1628 | | --- | --- | --- | 1629 | | `object` | `Object` | The object to inspect.| 1630 | 1631 | 1632 | #### Returns 1633 | 1634 | | Type | Description | 1635 | | --- | --- | 1636 | | `Array` | the array of property names.| 1637 | 1638 | ```js 1639 | function Foo() { 1640 | this.a = 1; 1641 | this.b = 2; 1642 | } 1643 | 1644 | Foo.prototype.c = 3; 1645 | 1646 | _.keysIn(new Foo); 1647 | // => ['a', 'b', 'c'] (iteration order is not guaranteed) 1648 | ``` 1649 | 1650 | ### valuesIn 1651 | 1652 | https://raw.githubusercontent.com/lodash/lodash/es6/object/valuesIn.js 1653 | 1654 | Creates an array of the own and inherited enumerable property values 1655 | 1656 | of `object`. 1657 | 1658 | 1659 | 1660 | **Note:** Non-object values are coerced to objects. 1661 | 1662 | #### Parameters 1663 | 1664 | | Name | Type | Description | 1665 | | --- | --- | --- | 1666 | | `object` | `Object` | The object to query.| 1667 | 1668 | 1669 | #### Returns 1670 | 1671 | | Type | Description | 1672 | | --- | --- | 1673 | | `Array` | the array of property values.| 1674 | 1675 | ```js 1676 | function Foo() { 1677 | this.a = 1; 1678 | this.b = 2; 1679 | } 1680 | 1681 | Foo.prototype.c = 3; 1682 | 1683 | _.valuesIn(new Foo); 1684 | // => [1, 2, 3] (iteration order is not guaranteed) 1685 | ``` 1686 | 1687 | ## chain 1688 | 1689 | ### thru 1690 | 1691 | https://raw.githubusercontent.com/lodash/lodash/es6/chain/thru.js 1692 | 1693 | This method is like `_.tap` except that it returns the result of `interceptor`. 1694 | 1695 | #### Parameters 1696 | 1697 | | Name | Type | Description | 1698 | | --- | --- | --- | 1699 | | `value` | `*` | The value to provide to `interceptor`.| 1700 | | `interceptor` | `Function` | The function to invoke.| 1701 | | `thisArg` | `*` | The `this` binding of `interceptor`.| 1702 | 1703 | 1704 | #### Returns 1705 | 1706 | | Type | Description | 1707 | | --- | --- | 1708 | | `*` | the result of `interceptor`.| 1709 | 1710 | ```js 1711 | _([1, 2, 3]) 1712 | .last() 1713 | .thru(function(value) { return [value]; }) 1714 | .value(); 1715 | // => [3] 1716 | ``` 1717 | 1718 | -------------------------------------------------------------------------------- /posts/harder-better-faster-stronger-lo-dash-v3/index.md: -------------------------------------------------------------------------------- 1 | [Lo-Dash](https://lodash.com/) is a utility library delivering consistency, customization, performance & extras. It is also one the most dependent upon NPM packages.[^https://www.npmjs.com/browse/depended] After a complete overhaul (over 800 commits since it has been bumped to v3.0.0-pre[^https://github.com/lodash/lodash/commit/1c770a3c66ef317eb6162fa121f6a46c3226d67f]), version 3 boosts [increased performance](#performance-improvements) and a whole lot of [new features](#added-methods). 2 | 3 | [3.0.0 version release notes](https://github.com/lodash/lodash/releases/tag/3.0.0) summarize the changes. 4 | 5 | ## Release Date 6 | 7 | Lo-Dash 3.0.0 ([lodash](#name)) has been released on 2015-01-26.[^https://twitter.com/jdalton/status/559771281705287680] 8 | 9 | ## Download 10 | 11 | Version 3.0.0 (2015-01-26) is available from the [NPM](https://www.npmjs.com/package/lodash): 12 | 13 | ```bash 14 | npm install lodash 15 | ``` 16 | 17 | ### Module Formats 18 | 19 | lodash is also available in a variety of other builds & module formats. 20 | 21 | * npm packages for [modern](https://www.npmjs.com/package/lodash), [compatibility](https://www.npmjs.com/package/lodash-compat), & [per method](https://www.npmjs.com/browse/keyword/lodash-modularized) builds 22 | * AMD modules for [modern](https://github.com/lodash/lodash/tree/3.0.0-amd) & [compatibility](https://github.com/lodash/lodash-compat/tree/3.0.0-amd) builds 23 | * ES modules for the [modern](https://github.com/lodash/lodash/tree/3.0.0-es) build 24 | 25 | ## Name 26 | 27 | With version 3.0.0, "Lo-Dash" has been renamed to "lodash".[^https://github.com/lodash/lodash/commit/ae98b995698ff5432656786b9e622953a3b28669][^https://twitter.com/jdalton/status/559090662209880064] 28 | 29 | ## Breaking Changes 30 | 31 | This post focuses on the new features. For breaking changes, refer to the [changelog](https://github.com/lodash/lodash/wiki/Changelog). 32 | 33 | ### Migration 34 | 35 | There is a [lodash-migrate](https://www.npmjs.com/package/lodash-migrate) package that assists with migrating code from older lodash versions to the latest release. 36 | 37 | ## Performance Improvements 38 | 39 | > We have improved performance 20-40% overall in v3 by better utilizing the JIT in JavaScript engines, using internal helper functions that avoid optimization disqualifications & increase the likelihood of function inlining. 40 | > 41 | > We are using Set & WeakMap for performance gains. This gives all modern browsers a boost & also means lodash will have significantly faster array/function methods in io.js over Node.js because io.js has Set/WeakMap on by default.[^https://github.com/lodash/lodash/releases/tag/3.0.0] 42 | 43 | * Improved overall performance 20-40%.[^https://twitter.com/jdalton/status/560110103148257282] 44 | * Method chaining supports [lazy evaluation](#lazy-evaluation). 45 | * Methods with support for [shortcut fusion](#shortcut-fusion): 46 | * _.drop 47 | * _.dropRight 48 | * _.dropRightWhile 49 | * _.dropWhile 50 | * _.filter 51 | * _.first 52 | * _.initial 53 | * _.last 54 | * _.map 55 | * _.pluck 56 | * _.reject 57 | * _.rest 58 | * _.reverse 59 | * _.slice 60 | * _.take 61 | * _.takeRight 62 | * _.takeRightWhile 63 | * _.takeWhile 64 | * _.where 65 | * Other optimized methods: 66 | * _.bind 67 | * _.clone 68 | * _.cloneDeep 69 | * _.compact 70 | * _.compose 71 | * _.contains 72 | * _.difference 73 | * _.escape 74 | * _.flatten 75 | * _.invoke 76 | * _.isEqual 77 | * _.isObject 78 | * _.matches 79 | * _.max 80 | * _.min 81 | * _.partial 82 | * _.shuffle 83 | * _.unescape 84 | * _.uniq 85 | * _.without 86 | * _.zip 87 | 88 | ## Custom Builds 89 | 90 | The official [changelog](https://github.com/lodash/lodash-cli/wiki/Changelog) have not been updated. Until that day, I have summarized the findings[^https://github.com/lodash/grunt-lodash/issues/15]. 91 | 92 | Refer to the official [changelog](https://github.com/lodash/lodash-cli/wiki/Changelog). 93 | 94 | ## ES6 95 | 96 | There is a [ES6 branch](https://github.com/lodash/lodash/tree/es) of Lo-Dash v3. It is using [ES6 modules](http://jsmodules.io/), Set & WeakMap, supports cloning typed arrays & aligns many methods to ES6.[^https://twitter.com/jdalton/status/541379703169220608] 97 | 98 | ## io.js 99 | 100 | Lo-Dash is [tested](https://travis-ci.org/lodash/lodash) and works with io.js.[^https://twitter.com/jdalton/status/555302574585155585] 101 | 102 | ## Lazy Evaluation 103 | 104 | In contrast to [eager evaluation](http://en.wikipedia.org/wiki/Eager_evaluation), lazy evaluation is an evaluation strategy which delays the evaluation of an expression until it is needed[^http://en.wikipedia.org/wiki/Lazy_evaluation]. Lo-Dash is using [lazy evaluation](http://en.wikipedia.org/wiki/Lazy_evaluation) to optimize the number of cycles needed to perform an operation. When result set sorting is not important and only a subset of the data is needed, this will result in a smaller memory footprint and smaller number of cycles. 105 | 106 | ### Example 107 | 108 | You have a collection of 100,000 products. Each product is described using a price. You need to get 5 products whose price is less than 0.5. There are two functions that are used to derive the product price, `derivePrice1` and `derivePrice2`. 109 | 110 | *Eager evaluation* will derive price of each product before proceeding to filter the collection. 111 | 112 | 113 | 114 | *Lazy evaluation* applies both functions to each member of the collection until `filter` and `take` conditions are satisfied. 115 | 116 | 117 | 118 | ### Shortcut Fusion 119 | 120 | Lazy evaluation uses [pipeline](http://en.wikipedia.org/wiki/Pipeline_%28computing%29) to process the intermediate data. Performing operation in a pipeline gets away without creating an array to store the intermediate results. Accessing the array once reduces the overhead, esp. when the data source is large and accessing memory is expensive. 121 | 122 | This does not affect Lo-Dash API. However, it is useful to know that your clunky array is handled in a resource efficient manner. 123 | 124 | 125 | 126 | ### Deferred Execution 127 | 128 | Lazy evaluation does not compute the chain until `.value()` is called (explicitly or implicitly). The benefit is that you can prepare a complex query and delay the execution until the time it is needed. 129 | 130 | 131 | 132 | ### Summary 133 | 134 | In some scenarios, lazy evaluation reduces the footprint of the application and the execution time. [Filip Zawada](https://twitter.com/filip_zawada) introduced lazy evaluation to Lo-Dash. He has written a [blog post](http://filimanjaro.com/blog/2014/introducing-lazy-evaluation/) about lazy evaluation in the context of Lo-Dash. 135 | 136 | Lazy evaluation is not a new concept in JavaScript. [lazy.js](http://danieltao.com/lazy.js/) is another utility library that implements lazy evaluation. Nevertheless, lazy evaluation is a nice addition to Lo-Dash that does not affect the API. 137 | 138 | ## Added Methods 139 | 140 | 47 new methods have been added.[^https://github.com/lodash/lodash/wiki/Changelog#notable-changes] The official documentation has not been released. I have written a [node script](https://github.com/gajus/blog.gajus.com/tree/master/post/lodash-v3/documentation-generator) that pulls each module from the [ES6 branch](https://github.com/lodash/lodash/tree/es) and parses the comments to [generate documentation](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md). 141 | 142 | ### String 143 | 144 | | Name | Description | 145 | | --- | --- | 146 | | [`camelCase`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#camelcase)| Converts `string` to camel case. See [Wikipedia](http://en.wikipedia.org/wiki/CamelCase) for more details. | 147 | | [`capitalize`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#capitalize)| Capitalizes the first character of `string`. | 148 | | [`deburr`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#deburr)| Used to match latin-1 supplementary letters (excluding mathematical operators). | 149 | | [`endsWith`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#endswith)| Checks if `string` ends with the given target string. | 150 | | [`escapeRegExp`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#escaperegexp)| Used to match `RegExp` special characters. See this [article on `RegExp` characters](http://www.regular-expressions.info/characters.html#special) for more details. | 151 | | [`kebabCase`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#kebabcase)| Converts `string` to kebab case (a.k.a. spinal case). See [Wikipedia](http://en.wikipedia.org/wiki/Letter_case#Computers) for more details. | 152 | | [`pad`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#pad)| Native method references. | 153 | | [`padLeft`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#padleft)| Pads `string` on the left side if it is shorter then the given padding length. The `chars` string may be truncated if the number of padding characters exceeds the padding length. | 154 | | [`padRight`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#padright)| Pads `string` on the right side if it is shorter then the given padding length. The `chars` string may be truncated if the number of padding characters exceeds the padding length. | 155 | | [`repeat`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#repeat)| Native method references. | 156 | | [`snakeCase`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#snakecase)| Converts `string` to snake case. See [Wikipedia](http://en.wikipedia.org/wiki/Snake_case) for more details. | 157 | | [`startsWith`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#startswith)| Checks if `string` starts with the given target string. | 158 | | [`trim`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#trim)| Removes leading and trailing whitespace or specified characters from `string`. | 159 | | [`trimLeft`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#trimleft)| Removes leading whitespace or specified characters from `string`. | 160 | | [`trimRight`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#trimright)| Removes trailing whitespace or specified characters from `string`. | 161 | | [`trunc`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#trunc)| Used as default options for `_.trunc`. | 162 | | [`words`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#words)| Used to match words to create compound words. | 163 | 164 | ### Array 165 | 166 | | Name | Description | 167 | | --- | --- | 168 | | [`chunk`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#chunk)| Native method references. | 169 | | [`dropRight`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#dropright)| Creates a slice of `array` with `n` elements dropped from the end. | 170 | | [`dropRightWhile`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#droprightwhile)| Creates a slice of `array` excluding elements dropped from the end. Elements are dropped until `predicate` returns falsey. The predicate is bound to `thisArg` and invoked with three arguments; (value, index, array). | 171 | | [`dropWhile`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#dropwhile)| Creates a slice of `array` excluding elements dropped from the beginning. Elements are dropped until `predicate` returns falsey. The predicate is bound to `thisArg` and invoked with three arguments; (value, index, array). | 172 | | [`flattenDeep`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#flattendeep)| Recursively flattens a nested array. | 173 | | [`pullAt`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#pullat)| Removes elements from `array` corresponding to the given indexes and returns an array of the removed elements. Indexes may be specified as an array of indexes or as individual arguments. **Note:** Unlike `_.at`, this method mutates `array`. | 174 | | [`slice`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#slice)| Creates a slice of `array` from `start` up to, but not including, `end`. **Note:** This function is used instead of `Array#slice` to support node lists in IE < 9 and to ensure dense arrays are returned. | 175 | | [`sortedLastIndex`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#sortedlastindex)| This method is like `_.sortedIndex` except that it returns the highest index at which `value` should be inserted into `array` in order to maintain its sort order. | 176 | | [`takeRight`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#takeright)| Creates a slice of `array` with `n` elements taken from the end. | 177 | | [`takeRightWhile`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#takerightwhile)| Creates a slice of `array` with elements taken from the end. Elements are taken until `predicate` returns falsey. The predicate is bound to `thisArg` and invoked with three arguments; (value, index, array). | 178 | | [`takeWhile`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#takewhile)| Creates a slice of `array` with elements taken from the beginning. Elements are taken until `predicate` returns falsey. The predicate is bound to `thisArg` and invoked with three arguments; (value, index, array). | 179 | 180 | ### Function 181 | 182 | | Name | Description | 183 | | --- | --- | 184 | | [`ary`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#ary)| Used to compose bitmasks for wrapper metadata. | 185 | | [`before`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#before)| Used as the `TypeError` message for "Functions" methods. | 186 | | [`curryRight`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#curryright)| Used to compose bitmasks for wrapper metadata. | 187 | | [`flow`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#flow)| Used as the `TypeError` message for "Functions" methods. | 188 | | [`negate`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#negate)| Used as the `TypeError` message for "Functions" methods. | 189 | | [`rearg`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#rearg)| Used to compose bitmasks for wrapper metadata. | 190 | 191 | ### Lang 192 | 193 | | Name | Description | 194 | | --- | --- | 195 | | [`isError`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#iserror)| `Object#toString` result references. | 196 | | [`isMatch`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#ismatch)| Used for native method references. | 197 | | [`isNative`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#isnative)| `Object#toString` result references. | 198 | | [`isTypedArray`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#istypedarray)| `Object#toString` result references. | 199 | | [`toPlainObject`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#toplainobject)| Converts `value` to a plain object flattening inherited enumerable properties of `value` to own properties of the plain object. | 200 | 201 | ### Utility 202 | 203 | | Name | Description | 204 | | --- | --- | 205 | | [`attempt`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#attempt)| Attempts to invoke `func`, returning either the result or the caught error object. | 206 | | [`matches`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#matches)| Creates a function which performs a deep comparison between a given object and `source`, returning `true` if the given object has equivalent property values, else `false`. | 207 | | [`propertyOf`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#propertyof)| The inverse of `_.property`; this method creates a function which returns the property value of a given key on `object`. | 208 | 209 | ### Collection 210 | 211 | | Name | Description | 212 | | --- | --- | 213 | | [`partition`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#partition)| Creates an array of elements split into two groups, the first of which contains elements `predicate` returns truthy for, while the second of which contains elements `predicate` returns falsey for. The predicate is bound to `thisArg` and invoked with three arguments; (value, index|key, collection). | 214 | | [`sortByAll`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#sortbyall)| This method is like `_.sortBy` except that it sorts by property names instead of an iteratee function. | 215 | 216 | ### Object 217 | 218 | | Name | Description | 219 | | --- | --- | 220 | | [`keysIn`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#keysin)| Used for native method references. | 221 | | [`valuesIn`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#valuesin)| Creates an array of the own and inherited enumerable property values of `object`. **Note:** Non-object values are coerced to objects. | 222 | 223 | ### Chain 224 | 225 | | Name | Description | 226 | | --- | --- | 227 | | [`thru`](https://github.com/gajus/blog.gajus.com/blob/master/post/harder-better-faster-stronger-lo-dash-v3/documentation.md#thru)| This method is like `_.tap` except that it returns the result of `interceptor`. | 228 | 229 | ## Summary 230 | 231 | Version 3 is the biggest update to Lo-Dash ever[^https://github.com/lodash/lodash/wiki/Changelog]. With ever increasing user base, modular code base, and cross-browser compatibility, Lo-Dash is the go-to utility library for years to come. With that in mind, there is an going competition with [lazy.js](https://github.com/dtao/lazy.js) and [undescore.js](http://underscorejs.org/), both of which are in active development. 232 | -------------------------------------------------------------------------------- /posts/mysql-sinsert/index.md: -------------------------------------------------------------------------------- 1 | I am working a lot with data that is identifiable using an external ID (EUID; external unique identifier), stored locally and augmented with additional metadata, e.g. 2 | 3 | ## Playground 4 | 5 | 1. Rotten Tomatoes API [`/movies`](http://developer.rottentomatoes.com/docs/read/json/v10/Movie_Info) and the corresponding resource (movie) id is what I am referring to as EUID. 6 | 2. `title` is a local representation of the external resource. 7 | 3. `title.foo` and `title.bar` is metadata. 8 | 9 | ```sql 10 | CREATE TABLE `title` ( 11 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 12 | `euid` int(10) unsigned NOT NULL, 13 | `foo` varchar(100) DEFAULT NULL, 14 | `bar` varchar(100) DEFAULT NULL, 15 | PRIMARY KEY (`id`), 16 | UNIQUE KEY `rottentomatoes_id` (`rottentomatoes_id`) 17 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 18 | ``` 19 | 20 | ## Requirements 21 | 22 | We need to fetch arbitrary records from the external API and augment it with arbitrary metadata. 23 | 24 | We need to perform `UPSERT` (update, insert): 25 | 26 | 1. Find a local record that represents the external resource using EUID. 27 | 1. If record does not exist, insert a record. 28 | 2. Update the local record. 29 | 3. Return local id. 30 | 31 | Returning local id is not part of the `UPSERT` concept. 32 | 33 | ## Reason for not using EUID as the primary key 34 | 35 | EUID can be anything. It can be a string ID (e.g. `2cd05416a2cbb410VgnVCM1000000b43151a____`) or it can be an unnecessary large integer (e.g. Facebook ids go up to 2^32). 36 | 37 | 1. You want to make your data (and indexes) as small as possible. Shorter indexes are faster, not only because they require less disk space, but because they also give you more hits in the index cache, and thus fewer disk seeks.[^http://dev.mysql.com/doc/refman/5.7/en/data-size.html] 38 | 2. If you are working with multiple external data sources, you want to have consistent PK format. 39 | 40 | ## Solutions 41 | 42 | `UPSERT` can be done using `SELECT[, INSERT], UPDATE` or `INSERT ... ON DUPLICATE KEY UPDATE`. 43 | 44 | ### `SELECT[, INSERT], UPDATE` 45 | 46 | > Code examples use ES7 [ecmascript-asyncawait](https://github.com/lukehoban/ecmascript-asyncawait) syntax to perform queries. This is no different from using Promises. 47 | 48 | ```js 49 | let Writer = (db) => { 50 | let writer = {}; 51 | 52 | /** 53 | * @param {Object} title 54 | * @param {String} title.id EUID 55 | * @param {String} title.foo 56 | * @param {String} title.bar 57 | * @return {Number} title id 58 | */ 59 | writer.upsertTitle = async (title) => { 60 | let titles, 61 | titleId; 62 | 63 | await db.query('START TRANSACTION'); 64 | 65 | titles = await db 66 | .query('SELECT `id` FROM `title` WHERE `euid` = ? LIMIT 1', [ 67 | title.id 68 | ]); 69 | 70 | if (titles.length) { 71 | titleId = titles[0].id; 72 | } else { 73 | titleId = await db 74 | .query('INSERT INTO `title` SET `euid` = ?', [ 75 | title.id 76 | ]) 77 | .then((result) => { 78 | return result.insertId; 79 | }); 80 | } 81 | 82 | await db 83 | .query('UPDATE `title` SET `foo` = ?, `bar` = ? WHERE `id` = ?', [ 84 | title.foo, 85 | title.bar, 86 | titleId 87 | ]); 88 | 89 | await db.query('COMMIT'); 90 | 91 | return titleId; 92 | }; 93 | 94 | return writer; 95 | }; 96 | ``` 97 | 98 | ### `INSERT ... ON DUPLICATE KEY UPDATE` 99 | 100 | ```js 101 | let Writer = (db) => { 102 | let writer = {}; 103 | 104 | /** 105 | * @param {Object} title 106 | * @param {String} title.id EUID 107 | * @param {String} title.foo 108 | * @param {String} title.bar 109 | * @return {Number} title id 110 | */ 111 | writer.upsertTitle = async (title) => { 112 | let titles; 113 | 114 | await db 115 | .query('INSERT INTO `title` SET `euid` = ?, `foo` = ?, `bar` = ? ON DUPLICATE KEY UPDATE `foo` = VALUES(`foo`), `bar` = VALUES(`bar`)', [ 116 | title.id, 117 | title.foo, 118 | title.bar 119 | ]); 120 | 121 | titles = await db 122 | .query('SELECT `id` FROM `title` WHERE `euid` = ? LIMIT 1', [ 123 | title.id 124 | ]); 125 | 126 | return titles[0].id; 127 | }; 128 | 129 | return writer; 130 | }; 131 | ``` 132 | 133 | ## Shortcomings 134 | 135 | ### `SELECT[, INSERT], UPDATE` 136 | 137 | * Verbose. 138 | * Requires that all other columns have a default value (or be nullable). 139 | 140 | ### `INSERT ... ON DUPLICATE KEY UPDATE` 141 | 142 | * Hard to analyzing query logs (e.g. identifying ratio between found/not found records). 143 | * `ON DUPLICATE KEY UPDATE` will not work when id is an auto-increment column. 144 | * You should try to avoid using an `ON DUPLICATE KEY UPDATE` clause on tables with multiple unique indexes.[^https://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html]. 145 | 146 | ## The solution 147 | 148 | With these considerations in mind, I prefer `SELECT[, INSERT], UPDATE` approach. 149 | 150 | * Verbosity is an easy problem to abstract. 151 | * Default value/[`NULL` problem](#null-problem) has its advantages. 152 | 153 | ### `SINSERT` 154 | 155 | I wrote a function that abstracts the look up of an existing record using a unique key and inserts the record when it is not found. I am calling it `SINSERT` (`SELECT[, INSERT]`): 156 | 157 | ```js 158 | /** 159 | * Selects a record using a unique identifier. 160 | * If record is not found, inserts a record using the unique identifier. 161 | * Returns id of the inserted record. 162 | * 163 | * @param {String} table 164 | * @param {String} column 165 | * @param {String|Number} uid 166 | * @return {Number} id 167 | */ 168 | db.sinsert = async (table, column, uid) => { 169 | let id; 170 | 171 | await db.query('START TRANSACTION'); 172 | 173 | id = db 174 | .query('SELECT `id` FROM ?? WHERE ?? = ?', [ 175 | table, 176 | column, 177 | uid 178 | ]) 179 | .then((rows) => { 180 | if (rows.length) { 181 | return rows[0].id; 182 | } 183 | 184 | return db 185 | .query('INSERT INTO ?? SET ?? = ?', [ 186 | table, 187 | column, 188 | uid 189 | ]) 190 | .then((result) => { 191 | return result.insertId; 192 | }); 193 | }); 194 | 195 | await db.query('COMMIT'); 196 | 197 | return id; 198 | }; 199 | ``` 200 | 201 | Using `SINSERT` we can continue to use EUID to retrieve reference to the local representation of the external resource, and use local id to perform business-as-usual `UPDATE` operation. 202 | 203 | ```js 204 | let Writer = (db) => { 205 | let writer = {}; 206 | 207 | /** 208 | * @param {Object} title 209 | * @param {String} title.id EUID 210 | * @param {String} title.foo 211 | * @param {String} title.bar 212 | * @return {Number} title id 213 | */ 214 | writer.upsertTitle = async (title) => { 215 | let titleId; 216 | 217 | titleId = await db.sinsert('title', 'euid', title.id); 218 | 219 | await db 220 | .query('UPDATE `title` SET `foo` = ?, `bar` = ? WHERE `id` = ?', [ 221 | title.foo, 222 | title.bar, 223 | titleId 224 | ]); 225 | 226 | return titleId; 227 | }; 228 | 229 | return writer; 230 | }; 231 | ``` 232 | 233 | ## `NULL` problem 234 | 235 | External resource representation in a local database must include EUID. This is the minimum knowledge required to: 236 | 237 | 1. Establish existence of the external resource. 238 | 2. Create a local representation of the resource. 239 | 3. Lookup data about the resource in the future. 240 | 241 | All metadata should be nullable to allow creating resource presence representation using the minimum available information. This is important. It enables separation of parsing and writing logic, e.g. 242 | 243 | ### TV shows, seasons and episodes 244 | 245 | You are working with an API that provides information about TV shows, seasons and episodes. You want to store information about an episode in a local database. 246 | 247 | You will need `episode` table that describes EUID, metadata and assigns a local resource id. You will have `show` and `season` tables describing the equivalent data. You can further normalise this by creating table `title` that represents the external resource collection. 248 | 249 | ```sql 250 | CREATE TABLE `title` ( 251 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 252 | `euid` varchar(255) NOT NULL DEFAULT '', 253 | PRIMARY KEY (`id`), 254 | UNIQUE KEY `euid` (`euid`) 255 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 256 | 257 | CREATE TABLE `episode` ( 258 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 259 | `title_id` int(10) unsigned NOT NULL, 260 | `season_id` int(10) unsigned DEFAULT NULL, 261 | `name` varchar(255) DEFAULT NULL, 262 | `number` int(10) unsigned DEFAULT NULL, 263 | PRIMARY KEY (`id`), 264 | UNIQUE KEY `season_id` (`season_id`,`number`), 265 | KEY `title_id` (`title_id`), 266 | CONSTRAINT `episode_ibfk_1` FOREIGN KEY (`title_id`) REFERENCES `title` (`id`) ON DELETE CASCADE, 267 | CONSTRAINT `episode_ibfk_2` FOREIGN KEY (`season_id`) REFERENCES `season` (`id`) ON DELETE CASCADE 268 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 269 | 270 | CREATE TABLE `season` ( 271 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 272 | `title_id` int(10) unsigned NOT NULL, 273 | `show_id` int(10) unsigned DEFAULT NULL, 274 | `number` int(10) unsigned DEFAULT NULL, 275 | PRIMARY KEY (`id`), 276 | UNIQUE KEY `title_id` (`title_id`), 277 | UNIQUE KEY `show_id` (`show_id`,`number`), 278 | CONSTRAINT `season_ibfk_1` FOREIGN KEY (`title_id`) REFERENCES `title` (`id`) ON DELETE CASCADE, 279 | CONSTRAINT `season_ibfk_2` FOREIGN KEY (`show_id`) REFERENCES `show` (`id`) ON DELETE CASCADE 280 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 281 | 282 | CREATE TABLE `show` ( 283 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 284 | `title_id` int(10) unsigned NOT NULL, 285 | `name` varchar(255) DEFAULT NULL, 286 | PRIMARY KEY (`id`), 287 | UNIQUE KEY `title_id` (`title_id`), 288 | CONSTRAINT `show_ibfk_1` FOREIGN KEY (`title_id`) REFERENCES `title` (`id`) ON DELETE CASCADE 289 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 290 | ``` 291 | 292 | You have made an API call to retrieve information about a TV episode. 293 | 294 | ```json 295 | { 296 | "id": "a9fc6741eb003410VgnVCM1000000b43151a____", 297 | "type": "episode", 298 | "name": "The Flamingo", 299 | "number": 1, 300 | "show": { 301 | "id": "ef10b1b8e1e50410VgnVCM1000000b43151a____" 302 | }, 303 | "season": { 304 | "id": "46e58fc0fbcd6410VgnVCM1000000b43151a____" 305 | } 306 | } 307 | ``` 308 | 309 | You want to write this information to the database. 310 | 311 | If `show` or `season` tables had non-nullable columns (e.g. show name or season number), you would not be able to insert record about TV episode without first fetching information about the TV show and season (assuming it is not already in the database). 312 | 313 | ```js 314 | let Writer = (db) => { 315 | let writer = {}; 316 | 317 | /** 318 | * @param {Object} episode 319 | * @param {String} episode.id Episode EUID. 320 | * @param {String} episode.name 321 | * @param {String} episode.number 322 | * @param {String} episode.show.id Show EUID. 323 | * @param {String} episode.season.id Season EUID. 324 | * @return {Number} episode id 325 | */ 326 | writer.upsertEpisode = (episode) => { 327 | let showId = await writer.sinsertShow(episode.show.id), 328 | seasonId = await writer.sinsertSeason(episode.season.id), 329 | episodeId = await writer.sinsertEpisode(episode.id); 330 | 331 | // Update. 332 | 333 | return episodeId; 334 | }; 335 | 336 | /** 337 | * @private 338 | * @param {String} EUID 339 | * @return {Number} title id 340 | */ 341 | writer.sinsertTitle = (EUID) => { 342 | return db.sinsert('title', 'euid', EUID); 343 | }; 344 | 345 | /** 346 | * @private 347 | * @param {String} Show EUID 348 | * @return {Number} show id 349 | */ 350 | writer.sinsertShow = async (EUID) => { 351 | let titleId = await writer.sinsertTitle(EUID); 352 | 353 | return db.sinsert('show', 'title_id', titleId); 354 | }; 355 | 356 | /** 357 | * @private 358 | * @param {String} Season EUID 359 | * @return {Number} season id 360 | */ 361 | writer.sinsertSeason = async (EUID) => { 362 | let titleId = await writer.sinsertTitle(EUID); 363 | 364 | return db.sinsert('season', 'title_id', titleId); 365 | }; 366 | 367 | /** 368 | * @private 369 | * @param {String} Episode EUID 370 | * @return {Number} episode id 371 | */ 372 | writer.sinsertEpisode = async (EUID) => { 373 | let titleId = await writer.sinsertTitle(EUID); 374 | 375 | return db.sinsert('episode', 'title_id', titleId); 376 | }; 377 | 378 | return writer; 379 | }; 380 | ``` 381 | 382 | This approach enables you to write data with complex relationships, but requires minimal knowledge about each player. Meta information about each player can be fetched in the future. 383 | -------------------------------------------------------------------------------- /posts/pro-angularjs/index.md: -------------------------------------------------------------------------------- 1 | This article will teach you about the individual components of AngularJS (1.3) and how they come together to make an application, i.e. you will known what is an AngularJS application and how to build it. This article is biased towards the [community conventions](#conventions) and therefore ignores uncommon/ill/edge use cases (e.g. using HTML comments to reference a directive from the template). This article assumes that you have a good understanding of JavaScript. 2 | 3 | All of the examples include references to the relevant documentation. If you are struggling to understand the included examples, refer to the documentation. All of the examples are in [JSFiddle](http://jsfiddle.net/). 4 | 5 | If this is your first encounter with AngularJS, please first read the official [Developer Guide](https://docs.angularjs.org/guide/introduction). It explains what type of applications should be developed using AngularJS and what good using AngularJS achieves. 6 | 7 | ## Single-Page Applications 8 | 9 | A typical [single-page application](http://en.wikipedia.org/wiki/Single-page_application) (SPA) starts with a stateless request to a server. The response is a document that executes the application. The application will load the necessary components (e.g. fragments of HTML and data to represent the current state of the application) in response to user actions. Typically, using [AJAJ](http://en.wikipedia.org/wiki/AJAJ). The goal is to provide a more fluid user experience akin to a desktop application. The page does not reload at any point in the process. 10 | 11 | In contrast, round-trip app logic and data resides on the server side; the browser acts as a rendering engine. The browser makes a series of stateless requests that the server side responds to with dynamically generated HTML. The drawbacks are: a lot of bandwidth waste, the user need to wait until the next HTML document is requested and loaded. 12 | 13 | AngularJS is used to develop single-page applications. 14 | 15 | ## REST 16 | 17 | Single-page application requires a web-service (backend) to perform [create, read, update and delete](http://en.wikipedia.org/wiki/Create,_read,_update_and_delete) (CRUD) operations. In modern web development, such service conforms to [representational state transfer](http://en.wikipedia.org/wiki/Representational_state_transfer) (REST) architecture. 18 | 19 | Two key players are the *URL* and the *request method*. The URL identifies the resource, and the HTTP method specifies what operation (CRUD) to perform. 20 | 21 | | Method | Intent | 22 | | --- | --- | 23 | | `GET` | Retrieve object specified by the URL. `GET` method is *nullipotent* (the same operation performed 0 or more times does not change the result). | 24 | | `PUT` | Update the object specified by the URL. `PUT` operation is *idempotent* (the same operation performed 1 or more times has the same effect on the data). | 25 | | `POST` | Create a new object (can perform double duty: update an object if it exists or create a new one if not) (neither nullipotent or idempotent). | 26 | | `DELETE` | Delete the object specified by the URL (idempotent). | 27 | 28 | ## MVW 29 | 30 | AngularJS advocates [Model-View-Whatever](https://plus.google.com/+AngularJS/posts/aZNVhj355G2) architectural pattern. Angular Controllers setup and add behavior to `$scope` objects, and Angular's [templates](#templates) and two-binding make the View layer, but Angular has virtually no opinion when it comes to your Model layer. You can do *whatever* you want.[^http://www.pseudobry.com/building-large-apps-with-angular-js/] 31 | 32 | Regardless of what we are calling it, it is important to separate the presentation logic from business logic and presentation state. To this extent, we are going to be calling it MVC. 33 | 34 | ### MVC 35 | 36 | | Player | Role | 37 | | --- | --- | 38 | | [Model](#model) | Logic that deals with storing and retrieving the data. | 39 | | [View](#view) | Logic that deals with formatting data to display to the user. | 40 | | [Controller](#controller) | Logic that responds to the user interactions, updates data in the model and provides data to the view. | 41 | 42 | #### Model 43 | 44 | Model should: 45 | 46 | * Contain the logic for CRUD the domain data (even if that means executing the remote logic via web services). 47 | * Define the [data domain](http://en.wikipedia.org/wiki/Data_domain). 48 | * Provide a clean API that exposes the model data and operations on it. 49 | 50 | Model should not: 51 | 52 | * Expose the details of how the model data is obtained or managed (in other words, details of the data storage mechanism or the remote web service should not be exposed to the controllers and views). 53 | * Contain the logic that transforms the model based on user interaction (this is the controller's job). 54 | * Contain logic for displaying data to the user (this is the view's job). 55 | 56 | The benefit of isolating model from the controller and view is that you can test your application logic more easily, and that enhancing and maintaining the overall application is simpler and easier. 57 | 58 | #### Controller 59 | 60 | Controller should: 61 | 62 | * Contain the logic required to initialise the scope. 63 | * Contain the logic/behaviours required by the view to present data from the scope. 64 | * Contain the logic/behaviours required to update the scope based on user interaction. 65 | 66 | Controller should not: 67 | 68 | * Contain logic that manipulates the DOM (that is the job for the view). 69 | * Contain logic that manages the persistence of data (that is the job for the model). 70 | * Manipulate data outside of the scope. 71 | 72 | #### View 73 | 74 | Views should: 75 | 76 | * Contain the logic and markup required to present data to the user. 77 | 78 | Views should not: 79 | 80 | * Contain complex logic (this is better placed in the controller). 81 | * Contain logic that creates, stores or manipulates the domain model. 82 | 83 | ### Common design pitfalls 84 | 85 | * Putting the logic in the wrong place (e.g. business logic in the view, rather than a controller; domain logic in the controller, rather than in a model; data store logic in the client model when using a RESTful service). 86 | 87 | * Manipulating the DOM directly using jQuery. 88 | 89 | ### Three rules to avoid the pitfalls 90 | 91 | * View logic should prepare data only for display and never modify the model. 92 | * Controller should never directly create, update or delete data from the model. 93 | * The client should never directly access the database. 94 | 95 | ## Conventions 96 | 97 | Angular puts itself forth as a [MVW](#mvw) framework (TL;DR you need conventions where framework does not enforce them). 98 | 99 | These are the most popular community driven conventions (in the order of the number of stars on GitHub): 100 | 101 | * [https://github.com/johnpapa/angularjs-styleguide](https://github.com/johnpapa/angularjs-styleguide) 102 | * [https://github.com/mgechev/angularjs-style-guide](https://github.com/mgechev/angularjs-style-guide) 103 | * [https://github.com/toddmotto/angularjs-styleguide](https://github.com/toddmotto/angularjs-styleguide) 104 | 105 | If you are starting with AngularJS, follow either (the most popular) of the existing style guides. Personal advise: if you disagree with a certain aspect of the convention, raise an issue and explain your reason for doing otherwise. If the vast majority of the community disagrees with you, chances are that you are better off to just go with the flow. 106 | 107 | 108 | 109 | ## Angular Components 110 | 111 | ### Functions 112 | 113 | Angular provides a number of [utility functions](https://docs.angularjs.org/api/ng/function) for common programming tasks: 114 | 115 | * [angular.lowercase](https://docs.angularjs.org/api/ng/function/angular.lowercase) 116 | * [angular.uppercase](https://docs.angularjs.org/api/ng/function/angular.uppercase) 117 | * [angular.forEach](https://docs.angularjs.org/api/ng/function/angular.forEach) 118 | * [angular.extend](https://docs.angularjs.org/api/ng/function/angular.extend) 119 | * [angular.noop](https://docs.angularjs.org/api/ng/function/angular.noop) 120 | * [angular.identity](https://docs.angularjs.org/api/ng/function/angular.identity) 121 | * [angular.isUndefined](https://docs.angularjs.org/api/ng/function/angular.isUndefined) 122 | * [angular.isDefined](https://docs.angularjs.org/api/ng/function/angular.isDefined) 123 | * [angular.isObject](https://docs.angularjs.org/api/ng/function/angular.isObject) 124 | * [angular.isString](https://docs.angularjs.org/api/ng/function/angular.isString) 125 | * [angular.isNumber](https://docs.angularjs.org/api/ng/function/angular.isNumber) 126 | * [angular.isDate](https://docs.angularjs.org/api/ng/function/angular.isDate) 127 | * [angular.isArray](https://docs.angularjs.org/api/ng/function/angular.isArray) 128 | * [angular.isFunction](https://docs.angularjs.org/api/ng/function/angular.isFunction) 129 | * [angular.isElement](https://docs.angularjs.org/api/ng/function/angular.isElement) 130 | * [angular.copy](https://docs.angularjs.org/api/ng/function/angular.copy) 131 | * [angular.equals](https://docs.angularjs.org/api/ng/function/angular.equals) 132 | * [angular.bind](https://docs.angularjs.org/api/ng/function/angular.bind) 133 | * [angular.toJson](https://docs.angularjs.org/api/ng/function/angular.toJson) 134 | * [angular.fromJson](https://docs.angularjs.org/api/ng/function/angular.fromJson) 135 | 136 | The list is limiting and depends on the needs of the Angular core development team. For this reason, I prefer the route that the [Backbone](http://backbonejs.org/) took. Backbone.js has a hard dependency for [Underscore](http://underscorejs.org/), which is a standalone collection of utility functions. While I don't recommend Angular team to have a hard dependency on an external utility function collection, I advise against using utility functions that are hard-coded into the framework and whose primary function is to support the framework. 137 | 138 | If you are starting a large scale application, I advise to not use either of the above functions and familiarize with either of the standalone utility libraries ([lodash](https://lodash.com/), [Underscore](http://underscorejs.org/), [lazy.js](http://danieltao.com/lazy.js/)). Whatever you choose, be consistent. 139 | 140 | The following functions are specific to AngularJS: 141 | 142 | #### angular.bootstrap 143 | 144 | Use [angular.bootstrap](https://docs.angularjs.org/api/ng/function/angular.bootstrap) to manually start up angular application. 145 | 146 | The example does not use the [ngApp](https://docs.angularjs.org/api/ng/directive/ngApp) directive to auto-bootstrap an application. 147 | 148 | 149 | 150 | #### angular.reloadWithDebugInfo 151 | 152 | Use [angular.reloadWithDebugInfo](https://docs.angularjs.org/api/ng/function/angular.reloadWithDebugInfo) to reload the current application with debug information turned on. 153 | 154 | Debug mode: 155 | 156 | * Attaches information about the bindings (`$binding` property of the [element's data](http://api.jquery.com/data/); data is in the jQlite scope and not in the `HTMLElement.dataset`). 157 | * Adds CSS classes ("ng-binding") to data-bound elements (as a result of `ngBind`, `ngBindHtml` or `{{...}}` interpolations). 158 | * Where the compiler has created a new scope, the scope and either "ng-scope" or "ng-isolated-scope" CSS class are attached to the corresponding element. These scope references can then be accessed via `element.scope()` and `element.isolateScope()`. 159 | 160 | Debug information is enabled by default. This function is used when [`$compileProvider.debugInfoEnabled()`](https://docs.angularjs.org/api/ng/provider/$compileProvider#debugInfoEnabled) has been used to disable debug information. 161 | 162 | [Disable debug information](https://docs.angularjs.org/guide/production#disabling-debug-data) in production for a significant performance boost. In production, you can call `angular.reloadWithDebugInfo()` in browser console to enable debug information. 163 | 164 | 165 | 166 | #### angular.injector 167 | 168 | [angular.injector](https://docs.angularjs.org/api/ng/function/angular.injector) creates an injector object (`$injector`). [`$injector`](https://docs.angularjs.org/api/auto/service/$injector) is used to retrieve object instances as defined by provider, instantiate types, invoke methods, and load modules. 169 | 170 | Angular creates a single $injector when it bootstraps an application and uses the single $injector to invoke controller functions, service functions, filter functions, and any other function that might need dependencies as parameters.[^http://odetocode.com/blogs/scott/archive/2014/01/13/meet-the-angularjs-injector.aspx]. You can obtain an instance of the injector: 171 | 172 | * If you manually bootstrap the application (using `angular.bootstrap()`). 173 | * Using `angular.element([DOM node]).injector()` where [DOM element] is a DOM element where the `ng-app` was defined (or a child element of this element). 174 | * Using DI, e.g. `$injector` parameter in the `module.config()`. 175 | 176 | `angular.injector()` is for creating a new instance of the `$injector`. 177 | 178 | Creating your own injector is useful in unit tests where you do not want singleton service instances. You must pass a list of the modules the injector will work with (just the core "ng" module in the above code). You have to explicitly list the ng module if you are going to use any services from the core of Angular. Unlike the angular.module method, which assumes you have a dependency on the ng module and will silently add "ng" to your list of dependencies, the injector function makes no assumptions about dependent modules.[^http://odetocode.com/blogs/scott/archive/2014/01/13/meet-the-angularjs-injector.aspx] 179 | 180 | 181 | 182 | #### angular.element 183 | 184 | [angular.element](https://docs.angularjs.org/api/ng/function/angular.element) wraps a raw DOM element or HTML string as a jQuery-like element. 185 | 186 | Angular is using a [built-in subset of jQuery](https://docs.angularjs.org/api/ng/function/angular.element#angular-s-jqlite), called "jQuery lite" or "jqLite". 187 | 188 | Refer to the official documentation of [angular.element](https://docs.angularjs.org/api/ng/function/angular.element) to learn about jQuery-lite specific methods and events. 189 | 190 | #### angular.module 191 | 192 | The [angular.module](https://docs.angularjs.org/api/ng/function/angular.module) is a global place for creating, registering and retrieving Angular modules. All modules (angular core or 3rd party) that should be available to an application must be registered using this mechanism. 193 | 194 | When passed two or more arguments, a new module is created. If passed only one argument, an existing module (the name passed as the first argument to module) is retrieved. 195 | 196 | A module is a collection of services, directives, controllers, filters, and configuration information. angular.module is used to configure the $injector. 197 | 198 | ## Templates 199 | 200 | In Angular, templates are written with HTML that contains Angular-specific elements and attributes. Angular combines the template with information from the model and controller to render the dynamic view that a user sees in the browser. 201 | 202 | AngularJS extends HTML in the following ways: 203 | 204 | * [Expressions & Data binding](#expressions-data-binding). 205 | * [Directives](#directives) 206 | 207 | ### Expressions & Data Binding 208 | 209 | AngularJS uses double-brace characters (`{{` and `}}`) to denote expression. The content of the expression is evaluated as a subset of JavaScript in the context of the `scope`. Expressions are evaluated using [$interpolate](https://docs.angularjs.org/api/ng/service/$interpolate) service. 210 | 211 | 212 | 213 | Angular expressions are like JavaScript expressions with the following differences: 214 | 215 | * [Context](#context) 216 | * [Forgiving](#forgiving) 217 | * You cannot use the following in an Angular expression: conditionals, loops, or exceptions. 218 | * You cannot declare functions in an Angular expression. 219 | * You cannot create regular expressions in an Angular expression. 220 | * You cannot use `,` or `void` in an Angular expression. 221 | * You can use [filters](https://docs.angularjs.org/api/ng/filter/filter) within expressions to format data before displaying it. 222 | 223 | #### Context 224 | 225 | In Angular, expressions are evaluated against a scope object, i.e. Angular expressions do not have access to global variables like `window`, `document` or `location`. 226 | 227 | In addition to implicit variables and inherited scope variables, every scope inherits these properties from the [$rootScope](https://docs.angularjs.org/api/ng/type/$rootScope.Scope): 228 | 229 | | Property | Description | 230 | | --- | --- | 231 | | `$id` | Unique scope ID (monotonically increasing) useful for debugging. | 232 | | `$parent` | Reference to the parent scope. | 233 | | `$root` | Reference to the root scope. | 234 | 235 | 236 | 237 | #### Forgiving 238 | 239 | Angular expression that evaluates to undefined scope property does not throw an error. 240 | 241 | 242 | 243 | ## Directives 244 | 245 | * Directives are functions. 246 | * Directives are referenced in HTML templates using element-names and attributes. 247 | * Directives define a custom behavior and transform the associated DOM element(s). 248 | * Angular has [inbuilt directives](https://docs.angularjs.org/api/ng/directive). 249 | * New directives can be declared using [`module.directive`](https://docs.angularjs.org/api/ng/type/angular.Module#directive). 250 | 251 | ### Example 252 | 253 | | Directive | Role | 254 | | --- | --- | 255 | | [`ng-app`](https://docs.angularjs.org/api/ng/directive/ngApp) | [Bootstraps](#bootstrap) an Angular application. | 256 | | [`ng-bind`](https://docs.angularjs.org/api/ng/directive/ngBind) | Attribute-reference equivalent of the [double-brace expression](#expressions-data-binding). | 257 | | `gajus-bar` | Custom directive. | 258 | | `gajus-baz` | Intentionally misspelled reference to `gajusBar` directive. | 259 | 260 | 261 | 262 | #### Referencing 263 | 264 | The [HTML compiler](https://docs.angularjs.org/guide/compiler) traverses the DOM matching directives against the DOM elements. HTML compiler must know the directive name before it can be matched in DOM. Elements that have names or attributes that do not resolve to an existing directive will be left intact. In the earlier example, `ngApp`, `ngBind` and `gajusBar` are resolved; `gajus-baz` is misspelled reference to `gajusBar`. Notice that there is no error; Angular ignores unknown elements and attributes. 265 | 266 | #### Naming 267 | 268 | * Directive declaration is in camelCase (e.g. `gajusBar`). 269 | * Directive reference is dash-delimited name (e.g. `gajus-bar`). 270 | * Inbuilt directives are prefixed with `ng`. 271 | * Custom directives must be prefixed with a short, unique and descriptive prefix.[^https://github.com/johnpapa/angularjs-styleguide#style-y073] 272 | 273 | 274 | 275 | ### Inbuilt 276 | 277 | Angular has many [inbuilt directives](https://docs.angularjs.org/api/ng/directive) for: 278 | 279 | * DOM control structures (repeating/hiding/conditionally including DOM fragments). 280 | * Event handling (click, submit, etc.). 281 | 282 | 283 | ### Custom 284 | 285 | * Directives are created using the [`directive`](https://docs.angularjs.org/api/ng/type/angular.Module#directive) method on an Angular `module`. 286 | * Directives are referenced from HTML templates using element-name or attribute. 287 | 288 | 289 | 290 | In the "foo" example, I am: 291 | 292 | * Defined "foo" module. 293 | * Defined "bar" directive. 294 | * Instructed directive to: 295 | * replace the referencing element with the template. 296 | * respond to attribute reference. 297 | * use string template 298 | 299 | 302 | 303 | ### Community 304 | 305 | There are a number of Angular directives made open-source by the community available for the re-use: 306 | 307 | * [http://ngmodules.org/](http://ngmodules.org/) 308 | * [https://github.com/search?l=JavaScript&q=angular+directive&type=Repositories](https://github.com/search?l=JavaScript&q=angular+directive&type=Repositories) 309 | 310 | 333 | 334 | ## Further Reading 335 | 336 | * [Pro AngularJS](http://www.apress.com/9781430264484) by Adam Freeman. 337 | * [Write Modern Web Apps with the MEAN Stack: Mongo, Express, AngularJS, and Node.js](http://www.peachpit.com/store/write-modern-web-apps-with-the-mean-stack-mongo-express-9780133962352) by Jeff Dickey 338 | * [D3 on AngularJS](https://leanpub.com/d3angularjs) by Ari Lerner and Victor Powell 339 | -------------------------------------------------------------------------------- /posts/the-definitive-guide-to-the-es7-async-functions/index.md: -------------------------------------------------------------------------------- 1 | ## Continuation-Passing Style 2 | 3 | Most JavaScript code that relies on asynchronous data is written using [continuation-passing style](http://en.wikipedia.org/wiki/Continuation-passing_style), e.g. 4 | 5 |
6 | on jsbin.com 7 |
8 | 9 | There are a few things that can help to minimize the so-called callback hell (viz., using named functions over anonymous functions, modularization)[^http://callbackhell.com/]. Nonetheless, sequential iterations remains a PITA. 10 | 11 |
12 | on jsbin.com 13 |
14 | 15 | ### ES6 Promise 16 | 17 | We can use [ES6 Promise](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise) to perform sequential iteration. 18 | 19 |
20 | on jsbin.com 21 |
22 | 23 | ## ES7 `async/await` 24 | 25 | If you are comfortable working with ES6 Promise, ES7 `async/await` is the sugar-coated equivalent. 26 | 27 | 28 | 29 | 30 | 31 | 32 | `await` keyword expects [ES6 Promise](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise): 33 | 34 | * If/when promise is resolved, the return value is the resolved value. 35 | * If/when promise is resolved, the [error thrown](#error-handling) is what been used to reject the promise. 36 | 37 | You can use `await` only in `async` function. 38 | 39 | ## ES7 `await*` 40 | 41 | [`await*`](https://github.com/lukehoban/ecmascript-asyncawait#await-and-parallelism) is a syntactic sugar for [`Promise.all`](https://github.com/petkaantonov/bluebird/blob/master/API.md#all---promise). 42 | 43 | 44 | 45 | ## Running ES7 Code 46 | 47 | [Async Functions for ECMAScript proposal](https://github.com/lukehoban/ecmascript-asyncawait) was accepted into stage 1 ("Proposal") of the ECMASCript 7 [spec process](https://docs.google.com/document/d/1QbEE0BsO4lvl7NFTn5WXWeiEIBfaVUF7Dk0hpPpPDzU) in January 2014. The spec text can be found [here](http://tc39.github.io/ecmascript-asyncawait/). 48 | 49 | You can run `async/await` code using a transpiler (source-to-source compiler). [Babel](https://babeljs.io/) transpiler has the biggest coverage of ES6 and ES7 features.[^http://kangax.github.io/compat-table/es7/] 50 | 51 | ```sh 52 | npm install babel@5.0.4 -g 53 | babel-node --stage 1 ./your-script.js 54 | ``` 55 | 56 | ## Choosing Between ES6 Promise and ES7 `async/await` 57 | 58 | `async/await` makes the biggest difference when you have multiple dependencies that need to be fetched asynchronously, e.g. (the requirement is to have all three connections open at once) 59 | 60 | 61 | 62 | Of course, even the latter can be flattened using Bluebird [.spread()](https://github.com/petkaantonov/bluebird/blob/master/API.md#spreadfunction-fulfilledhandler--function-rejectedhandler----promise): 63 | 64 | 65 | 66 | Nonetheless, I prefer `await` keyword: 67 | 68 | 69 | 70 | [add example showing how each/map/then chaining work together with await] 71 | 72 | ## Error Handling 73 | 74 | You must wrap all instances of `await` in a `try...catch` statement. 75 | 76 | 77 | 78 | The promise that we await for is rejected. Rejection terminates the `async` block but does not propagate to the program. The output of the above program: 79 | 80 | ``` 81 | a 82 | 1 83 | b 84 | [Error: Oops #0] 85 | ``` 86 | 87 | Take a moment to observe that `[Error: Oops #0]` comes after `b` and `1` does not. This is because the `async` function is defined as IIFE. To delay 88 | 89 | As the code base grows it becomes harder and harder to handle errors within `async` functions. 90 | 91 | ### Global Error Handler 92 | 93 | Remember that beneath the surface, `await` simply handles ES6 [Promise](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise). 94 | 95 | You can use [Bluebird](https://github.com/petkaantonov/bluebird) Promise implementation to take advantage of the [global rejection events](https://github.com/petkaantonov/bluebird/blob/master/API.md#global-rejection-events). 96 | 97 | 98 | 99 | ## Testing `async/await` 100 | 101 | When I started using `async/await` my first test cases were these: 102 | 103 | 104 | 105 | Remember that beneath the surface, `await` simply handles ES6 [Promise](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise). 106 | 107 | You can use your regular testing strategy that you use when testing Promise objects. I use [`chai-as-promised`](https://github.com/domenic/chai-as-promised/): 108 | 109 | 110 | 111 | My experience was that using Promise directly for testing has prooved to be more reliable then experimental `async/await`. 112 | -------------------------------------------------------------------------------- /posts/the-definitive-guide-to-the-javascript-generators/generators.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gajus/gajus.com-blog/a91760feeb9198eb6ab32a81895fdfb31d05b7ae/posts/the-definitive-guide-to-the-javascript-generators/generators.gif -------------------------------------------------------------------------------- /posts/the-definitive-guide-to-the-javascript-generators/index.md: -------------------------------------------------------------------------------- 1 | There are many articles [^http://odetocode.com/blogs/scott/archive/2014/02/17/thoughts-on-javascript-generators.aspx] [^http://truffles.me.uk/playing-with-es6-generators-to-make-a-maybe-in-javascript] [^http://devsmash.com/blog/whats-the-big-deal-with-generators] [^http://luisvega.me/understanding-node-generators] [^http://webreflection.blogspot.com/2013/06/on-harmony-javascript-generators.html] [^http://blog.alexmaccaw.com/how-yield-will-transform-node] [^http://tobyho.com/2013/06/16/what-are-generators/] about JavaScript generators. I have read them all and nonetheless I have struggled to understand the execution order and what are the use cases. I have summarized the learning process that got me to understanding ES6 generators. 2 | 3 | ## Building an Iterator from a Generator 4 | 5 | ```js 6 | // tonic ^6.0.0 7 | const generatorFunction = function* () {}; 8 | const iterator = generatorFunction(); 9 | 10 | console.log(iterator[Symbol.iterator]); 11 | 12 | // function [Symbol.iterator]() 13 | ``` 14 | 15 | `generatorFunction` variable is assigned a generator function. Generator functions are denoted using `function*` syntax. 16 | 17 | Calling a generator function returns an iterator object. 18 | 19 | ```js 20 | // tonic ^6.0.0 21 | const generatorFunction = function* () { 22 | // This does not get executed. 23 | console.log('a'); 24 | }; 25 | 26 | console.log(1); 27 | const iterator = generatorFunction(); 28 | console.log(2); 29 | 30 | // 1 31 | // 2 32 | ``` 33 | 34 | ## Advancing the Generator 35 | 36 | `next()` method is used to advance the execution of the generator body: 37 | 38 | ```js 39 | // tonic ^6.0.0 40 | const generatorFunction = function* () { 41 | console.log('a'); 42 | }; 43 | 44 | console.log(1); 45 | const iterator = generatorFunction(); 46 | console.log(2); 47 | iterator.next(); 48 | console.log(3); 49 | 50 | // 1 51 | // 2 52 | // a 53 | // 3 54 | ``` 55 | 56 | `next()` method returns an object that indicates the progress of the iteration: 57 | 58 | ```js 59 | // tonic ^6.0.0 60 | const generatorFunction = function* () {}; 61 | const iterator = generatorFunction(); 62 | 63 | console.log(iterator.next()); 64 | 65 | // Object {value: undefined, done: true} 66 | ``` 67 | 68 | `done` property indicates that the generator body has been run to the completion. 69 | 70 | The generator function is expected to utilize `yield` keyword. `yield` suspends execution of a generator and returns control to the iterator. 71 | 72 | ```js 73 | // tonic ^6.0.0 74 | const generatorFunction = function* () { 75 | yield; 76 | }; 77 | const iterator = generatorFunction(); 78 | 79 | console.log(iterator.next()); 80 | console.log(iterator.next()); 81 | 82 | // Object {value: undefined, done: false} 83 | // Object {value: undefined, done: true} 84 | ``` 85 | 86 | When suspended, the generator does not block the event queue: 87 | 88 | ```js 89 | // tonic ^6.0.0 90 | const generatorFunction = function* () { 91 | var i = 0; 92 | while (true) { 93 | yield i++; 94 | } 95 | }; 96 | 97 | const iterator = generatorFunction(); 98 | 99 | console.log(iterator.next()); 100 | console.log(iterator.next()); 101 | console.log(iterator.next()); 102 | console.log(iterator.next()); 103 | console.log(iterator.next()); 104 | console.log(iterator.next()); 105 | 106 | // Object {value: 0, done: false} 107 | // Object {value: 1, done: false} 108 | // Object {value: 2, done: false} 109 | // Object {value: 3, done: false} 110 | // Object {value: 4, done: false} 111 | // Object {value: 5, done: false} 112 | ``` 113 | 114 | ## Pass a Value To the Iterator 115 | 116 | `yield` keyword can pass a value back to the iterator: 117 | 118 | ```js 119 | // tonic ^6.0.0 120 | const generatorFunction = function* () { 121 | yield 'foo'; 122 | }; 123 | 124 | iterator = generatorFunction(); 125 | 126 | console.log(iterator.next()); 127 | console.log(iterator.next()); 128 | 129 | // Object {value: "foo", done: false} 130 | // Object {value: undefined, done: true} 131 | ``` 132 | 133 | Any data type can be yielded, including functions, numbers, arrays and objects. 134 | 135 | When the generator is advanced to the completion, the `return` value is returned. 136 | 137 | ```js 138 | // tonic ^6.0.0 139 | const generatorFunction = function* () { 140 | yield 'foo'; 141 | return 'bar'; 142 | }; 143 | 144 | const iterator = generatorFunction(); 145 | 146 | console.log(iterator.next()); 147 | console.log(iterator.next()); 148 | 149 | // Object {value: "foo", done: false} 150 | // Object {value: "bar", done: true} 151 | ``` 152 | 153 | ## Receive a Value From the Iterator 154 | 155 | `yield` keyword can receive a value back from the iterator: 156 | 157 | ```js 158 | // tonic ^6.0.0 159 | const generatorFunction = function* () { 160 | console.log(yield); 161 | }; 162 | 163 | const iterator = generatorFunction(); 164 | 165 | iterator.next('foo'); 166 | iterator.next('bar'); 167 | 168 | // bar 169 | ``` 170 | 171 | There is no `yield` expression to receive the first value "foo". The value is tossed-away. 172 | 173 | ## Understanding the Execution Flow 174 | 175 | The best way to understand the execution flow of the generators is to play around using a `debugger`. I have illustrated the example that I have used to wrap my head around the I/O order. 176 | 177 |
178 | 179 |
Animated execution flow of the ES6 generators.
180 |
181 | 182 | ## Iterating Using the `for...of` Statement 183 | 184 | The iterator object returned from the generator is compliant with the ["iterable"](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/iterable) protocol. Therefore, you can use the [`for...of` statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of) to loop through the generator. 185 | 186 | ```js 187 | // tonic ^6.0.0 188 | let index; 189 | 190 | const generatorFunction = function* () { 191 | yield 1; 192 | yield 2; 193 | yield 3; 194 | return 4; 195 | }; 196 | 197 | const iterator = generatorFunction(); 198 | 199 | for (index of iterator) { 200 | console.log(index); 201 | } 202 | 203 | // 1 204 | // 2 205 | // 3 206 | ``` 207 | 208 | * The iteration will continue as long as `done` property is `false`. 209 | * The `for..of` loop cannot be used in cases where you need to pass in values to the generator steps. 210 | * The `for..of` loop will throw away the `return` value. 211 | 212 | ## Delegating `yield` 213 | 214 | The `yield*`operator delegates to another generator. 215 | 216 | ```js 217 | // tonic ^6.0.0 218 | let index; 219 | 220 | const foo = function* () { 221 | yield 'foo'; 222 | yield * bar(); 223 | }; 224 | 225 | const bar = function* () { 226 | yield 'bar'; 227 | yield * baz(); 228 | }; 229 | 230 | const baz = function* () { 231 | yield 'baz'; 232 | }; 233 | 234 | for (index of foo()) { 235 | console.log(index); 236 | } 237 | 238 | // foo 239 | // bar 240 | // baz 241 | ``` 242 | 243 | Delegating a generator to another generator is in effect the same as importing the body of the target generator to the destination generator. For illustration purposes only, the above code unfolds to the following: 244 | 245 | ```js 246 | // tonic ^6.0.0 247 | let index; 248 | 249 | const foo = function* () { 250 | yield 'foo'; 251 | yield 'bar'; 252 | yield 'baz'; 253 | }; 254 | 255 | for (index of foo()) { 256 | console.log(index); 257 | } 258 | 259 | // foo 260 | // bar 261 | // baz 262 | ``` 263 | 264 | ## Throw 265 | 266 | In addition to advancing the generator instance using `next()`, you can `throw()`. Whatever is thrown will propagate back up into the code of the generator, i.e. it can be handled either within or outside the generator instance: 267 | 268 | ```js 269 | // tonic ^6.0.0 270 | const generatorFunction = function* () { 271 | while (true) { 272 | try { 273 | yield; 274 | } catch (e) { 275 | if (e != 'a') { 276 | throw e; 277 | } 278 | console.log('Generator caught', e); 279 | } 280 | } 281 | }; 282 | 283 | const iterator = generatorFunction(); 284 | 285 | iterator.next(); 286 | 287 | try { 288 | iterator.throw('a'); 289 | iterator.throw('b'); 290 | } catch (e) { 291 | console.log('Uncaught', e); 292 | } 293 | 294 | // Generator caught a 295 | // Uncaught b 296 | ``` 297 | 298 | Any data type can be thrown, including functions, numbers, arrays and objects. 299 | 300 | ## What Problem Do Generators Solve? 301 | 302 | In JavaScript, IO operations are generally done as asynchronous operations that require a callback. For the purpose of illustration, I am going to use a made-up service `foo`: 303 | 304 | ```js 305 | // tonic ^6.0.0 306 | const foo = (name, callback) => { 307 | setTimeout(() => { 308 | callback(name); 309 | }, 100); 310 | }; 311 | ``` 312 | 313 | Multiple asynchronous operations one after another produce nesting that is hard to read. 314 | 315 | ```js 316 | // tonic ^6.0.0 317 | const foo = (name, callback) => { 318 | setTimeout(() => { 319 | callback(name); 320 | }, 100); 321 | }; 322 | 323 | foo('a', (a) => { 324 | foo('b', (b) => { 325 | foo('c', (c) => { 326 | console.log(a, b, c); 327 | }); 328 | }); 329 | }); 330 | 331 | // a 332 | // b 333 | // c 334 | ``` 335 | 336 | There are several solutions to address the issue, such as [using promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) or generators. Using generators, the above code can be rewritten as such: 337 | 338 | ```js 339 | // tonic ^6.0.0 340 | (function* () { 341 | const a = yield curry(foo, 'a'); 342 | const b = yield curry(foo, 'b'); 343 | const c = yield curry(foo, 'c'); 344 | 345 | console.log(a, b, c); 346 | }); 347 | ``` 348 | 349 | To execute the generator, we need a controller. The controller needs to fulfill the asynchronous requests and return the result back. 350 | 351 | ```js 352 | // tonic ^6.0.0 353 | /** 354 | * Initiates a generator and iterates through each function supplied 355 | * via the yield operator. 356 | * 357 | * @param {Function} 358 | */ 359 | const controller = (generator) => { 360 | const iterator = generator(); 361 | 362 | const advancer = (response) => { 363 | // Advance the iterator using the response of an asynchronous callback. 364 | const state = iterator.next(response); 365 | 366 | if (!state.done) { 367 | // Make the asynchronous function call the advancer. 368 | state.value(advancer); 369 | } 370 | } 371 | 372 | advancer(); 373 | }; 374 | ``` 375 | 376 | The last step is to curry the asynchronous functions into functions that take a single parameter (the callback). This allows to iterate the generator instance knowing that `yield` expression is always expecting a single parameter, the callback that is used to further advance the iteration. 377 | 378 | ```js 379 | // tonic ^6.0.0 380 | /** 381 | * Transforms a function that takes multiple arguments into a 382 | * function that takes just the last argument of the original function. 383 | * 384 | * @param {Function} 385 | * @param {...*} 386 | */ 387 | const curry = (method, ...args) => { 388 | return (callback) => { 389 | args.push(callback); 390 | 391 | return method.apply({}, args); 392 | }; 393 | }; 394 | ``` 395 | 396 | The end result is a script without too many levels of nested callbacks and achieved line independence (the code for one operation is no longer tied to the ones that come after it). 397 | 398 | ```js 399 | // tonic ^6.0.0 400 | const foo = (name, callback) => { 401 | setTimeout(() => { 402 | callback(name); 403 | }, 100); 404 | }; 405 | 406 | const curry = (method, ...args) => { 407 | return (callback) => { 408 | args.push(callback); 409 | 410 | return method.apply({}, args); 411 | }; 412 | }; 413 | 414 | const controller = (generator) => { 415 | const iterator = generator(); 416 | 417 | const advancer = (response) => { 418 | var state; 419 | 420 | state = iterator.next(response); 421 | 422 | if (!state.done) { 423 | state.value(advancer); 424 | } 425 | } 426 | 427 | advancer(); 428 | }; 429 | 430 | controller(function* () { 431 | const a = yield curry(foo, 'a'); 432 | const b = yield curry(foo, 'b'); 433 | const c = yield curry(foo, 'c'); 434 | 435 | console.log(a, b, c); 436 | }); 437 | 438 | // a 439 | // b 440 | // c 441 | ``` 442 | 443 | ## Error Handling 444 | 445 | It is common to handle the error handling for each individual asynchronous operation, e.g. 446 | 447 | ```js 448 | // tonic ^6.0.0 449 | const foo = (name, callback) => { 450 | callback(null, name); 451 | }; 452 | 453 | foo('a', (error1, result1) => { 454 | if (error1) { 455 | throw new Error(error1); 456 | } 457 | 458 | foo('b', (error2, result2) => { 459 | if (error2) { 460 | throw new Error(error2); 461 | } 462 | 463 | foo('c', (error3, result3) => { 464 | if (error3) { 465 | throw new Error(error3); 466 | } 467 | 468 | console.log(result1, result2, result3); 469 | }); 470 | }); 471 | }); 472 | 473 | // a 474 | // b 475 | // c 476 | ``` 477 | 478 | In the following example, I enable the controller to throw an error and use `try...catch` block to capture all errors. 479 | 480 | ```js 481 | // tonic ^6.0.0 482 | const foo = (parameters, callback) => { 483 | setTimeout(() => { 484 | callback(parameters); 485 | }, 100); 486 | }; 487 | 488 | const curry = (method, ...args) => { 489 | return (callback) => { 490 | args.push(callback); 491 | 492 | return method.apply({}, args); 493 | }; 494 | }; 495 | 496 | const controller = (generator) => { 497 | const iterator = generator(); 498 | 499 | const advancer = (response) => { 500 | if (response && response.error) { 501 | return iterator.throw(response.error); 502 | } 503 | 504 | const state = iterator.next(response); 505 | 506 | if (!state.done) { 507 | state.value(advancer); 508 | } 509 | } 510 | 511 | advancer(); 512 | }; 513 | 514 | controller(function* () { 515 | let a, 516 | b, 517 | c; 518 | 519 | try { 520 | a = yield curry(foo, 'a'); 521 | b = yield curry(foo, {error: 'Something went wrong.'}); 522 | c = yield curry(foo, 'c'); 523 | } catch (e) { 524 | console.log(e); 525 | } 526 | 527 | console.log(a, b, c); 528 | }); 529 | 530 | // Something went wrong. 531 | // a undefined undefined 532 | 533 | ``` 534 | 535 | Notice that the execution was interrupted before `curry(foo, 'c')` was called. 536 | 537 | ## Libraries To Streamline Generator Based Flow-Control 538 | 539 | There are several existing libraries that implement a variation of the above controller, as well as offer interoperability with promises, trunks and other techniques. 540 | 541 | ## Further Reading 542 | 543 | [Exploring ES6](http://exploringjs.com/) has a chapter about [Generators](http://exploringjs.com/es6/ch_generators.html). [Axel Rauschmayer](https://twitter.com/rauschma) [write up](http://2ality.com/2015/03/es6-generators.html) about generators covers a lot more than I managed to cover in this article. It is a lengthy read, though I thoroughly recommend it. 544 | 545 | * https://github.com/jmar777/suspend 546 | * https://github.com/visionmedia/co 547 | * https://github.com/bjouhier/galaxy 548 | * https://github.com/spion/genny 549 | * https://github.com/creationix/gen-run 550 | -------------------------------------------------------------------------------- /posts/using-dataloader-to-batch-requests/index.md: -------------------------------------------------------------------------------- 1 | Facebook [DataLoader](https://github.com/facebook/dataloader) is a generic utility used to abstract request batching and caching. 2 | 3 | I promise you – this thing is magic. 4 | 5 | I have discovered DataLoader as part of my research to solve N+1 problem arising when using GraphQL. DataLoader is referred to as a solution to reduce the number of round-trips. However, I have read the documentation and it wasn't immediately clear what it does or how it works; at first sight, it appeared to be a simple key-value cache storage with a getter function. 6 | 7 | In this post I illustrate how DataLoader achieves request batching. 8 | 9 | First of all, lets create an example of the N+1 problem. 10 | 11 | ## N+1 problem 12 | 13 | The N+1 problem arises when you have a data collection and each child of that collection owns another collection, e.g. a list of posts and each post has a list of tags. 14 | 15 | A naive object-relational mapping (ORM) implementation will first fetch the posts and then will make a query for each post to get the tags. Using an example of MySQL, that would result in 5 queries: 16 | 17 | ```sql 18 | mysql> SELECT * FROM `post`; 19 | +----+------+ 20 | | id | name | 21 | +----+------+ 22 | | 1 | huhu | 23 | | 2 | lala | 24 | | 3 | keke | 25 | | 4 | koko | 26 | +----+------+ 27 | 4 rows in set (0.00 sec) 28 | 29 | mysql> SELECT `tag`.*, `post_tag`.`post_id` FROM `post_tag` INNER JOIN `tag` ON `tag`.`id` = `post_tag`.`tag_id` WHERE `post_tag`.`post_id` = 1; 30 | +----+------+---------+ 31 | | id | name | post_id | 32 | +----+------+---------+ 33 | | 1 | toto | 1 | 34 | | 2 | titi | 1 | 35 | +----+------+---------+ 36 | 2 rows in set (0.00 sec) 37 | 38 | mysql> SELECT `tag`.*, `post_tag`.`post_id` FROM `post_tag` INNER JOIN `tag` ON `tag`.`id` = `post_tag`.`tag_id` WHERE `post_tag`.`post_id` = 2; 39 | +----+------+---------+ 40 | | id | name | post_id | 41 | +----+------+---------+ 42 | | 1 | toto | 2 | 43 | | 1 | toto | 2 | 44 | +----+------+---------+ 45 | 2 rows in set (0.00 sec) 46 | 47 | mysql> SELECT `tag`.*, `post_tag`.`post_id` FROM `post_tag` INNER JOIN `tag` ON `tag`.`id` = `post_tag`.`tag_id` WHERE `post_tag`.`post_id` = 3; 48 | +----+------+---------+ 49 | | id | name | post_id | 50 | +----+------+---------+ 51 | | 4 | tutu | 3 | 52 | +----+------+---------+ 53 | 1 row in set (0.00 sec) 54 | 55 | mysql> SELECT `tag`.*, `post_tag`.`post_id` FROM `post_tag` INNER JOIN `tag` ON `tag`.`id` = `post_tag`.`tag_id` WHERE `post_tag`.`post_id` = 4; 56 | +----+------+---------+ 57 | | id | name | post_id | 58 | +----+------+---------+ 59 | | 1 | toto | 4 | 60 | | 2 | titi | 4 | 61 | +----+------+---------+ 62 | 2 rows in set (0.00 sec) 63 | ``` 64 | 65 | ## ORMs are smart 66 | 67 | A lot of the ORMs optimize queries to fetch collection data. Here is an example using [Bookshelf](http://bookshelfjs.org/) ORM to fetch the data from the previous example: 68 | 69 | ```js 70 | import createKnex from 'knex'; 71 | import createBookshelf from 'bookshelf'; 72 | 73 | const knex = createKnex({ 74 | client: 'mysql', 75 | connection: { 76 | host: '127.0.0.1', 77 | user: 'root', 78 | database: 'blog' 79 | } 80 | }); 81 | 82 | const bookshelf = createBookshelf(knex); 83 | 84 | const Post = bookshelf.Model.extend({ 85 | tableName: 'post', 86 | tags: function () { 87 | return this.belongsToMany(Tag) 88 | } 89 | }); 90 | 91 | const Tag = bookshelf.Model.extend({ 92 | tableName: 'tag' 93 | }); 94 | 95 | bookshelf 96 | .Collection 97 | .extend({ 98 | model: Post 99 | }) 100 | .forge() 101 | .fetch({ 102 | withRelated: 'tags' 103 | }); 104 | ``` 105 | 106 | The latter will fetch data using two queries: 107 | 108 | ```sql 109 | select 110 | `post`.* 111 | from 112 | `post`; 113 | 114 | select 115 | `tag`.*, 116 | `post_tag`.`post_id` as `_pivot_post_id`, 117 | `post_tag`.`tag_id` as `_pivot_tag_id` 118 | from 119 | `tag` 120 | inner join 121 | `post_tag` 122 | on 123 | `post_tag`.`tag_id` = `tag`.`id` 124 | where 125 | `post_tag`.`post_id` in (?, ?, ?, ?); 126 | ``` 127 | 128 | The problem arises when you know only the parent node when requesting its dependencies. You will observe this patten in a system where each node is responsible for fetching its own dependencies, e.g. GraphQL. 129 | 130 | ## GraphQL nested queries 131 | 132 | One of the key aspects of [GraphQL](http://graphql.org/) is its ability to nest queries. 133 | 134 | To continue with the blog example, lets implement a schema that would enable us to fetch all posts and their tags, i.e. a schema that supports the following query: 135 | 136 | ```graphql 137 | { 138 | posts { 139 | id, 140 | name, 141 | tags { 142 | id, 143 | name 144 | } 145 | } 146 | } 147 | ``` 148 | 149 | The schema implementation: 150 | 151 | ```js 152 | import { 153 | graphql, 154 | GraphQLID, 155 | GraphQLInt, 156 | GraphQLList, 157 | GraphQLNonNull, 158 | GraphQLObjectType, 159 | GraphQLSchema, 160 | GraphQLString 161 | } from 'graphql'; 162 | 163 | // For definition of Post and Tag, refer 164 | // to the previous examples in the article. 165 | import { 166 | Post, 167 | Tag 168 | } from './models'; 169 | 170 | const TagType = new GraphQLObjectType({ 171 | name: 'Tag', 172 | fields: () => { 173 | return { 174 | id: { 175 | type: new GraphQLNonNull(GraphQLID) 176 | }, 177 | name: { 178 | type: new GraphQLNonNull(GraphQLString) 179 | } 180 | }; 181 | } 182 | }); 183 | 184 | const PostType = new GraphQLObjectType({ 185 | name: 'Post', 186 | fields: () => { 187 | return { 188 | id: { 189 | type: new GraphQLNonNull(GraphQLID) 190 | }, 191 | name: { 192 | type: GraphQLString 193 | }, 194 | tags: { 195 | type: new GraphQLList(TagType), 196 | resolve: (post) => { 197 | return Post 198 | .forge({ 199 | id: post.id 200 | }) 201 | .load('tags') 202 | .call('related', 'tags') 203 | .call('toJSON'); 204 | } 205 | } 206 | }; 207 | } 208 | }); 209 | 210 | const QueryType = new GraphQLObjectType({ 211 | name: 'Query', 212 | fields: { 213 | posts: { 214 | type: new GraphQLList(PostType), 215 | resolve: (root) => { 216 | return Post 217 | .fetchAll() 218 | .call('toJSON'); 219 | } 220 | } 221 | } 222 | }); 223 | 224 | const schema = new GraphQLSchema({ 225 | query: QueryType 226 | }); 227 | ``` 228 | 229 | This example assumes that you have at least minimal knowledge of GraphQL. If you are not familiar with GraphQL, then a simple takeaway is: 230 | 231 | * `posts` node implements `resolve` method that fetches data for all `Post` models. 232 | * `post` has a property `tags`. `tags` `resolve` method has access to the data about the post. 233 | * `post` has a property `tags`. `tags` `resolve` method fetches data for all `Tag` models using the post `id` value. 234 | 235 | In this example, `tags` method only knows about a single post at the time of being called, i.e. it does not know whether you are fetching information about a single post or many posts. As a result, executing the earlier query will result in N+1 problem, e.g. 236 | 237 | ```js 238 | const query = `{ 239 | posts { 240 | id, 241 | name, 242 | tags { 243 | id, 244 | name 245 | } 246 | } 247 | }`; 248 | 249 | graphql(schema, query) 250 | ``` 251 | 252 | The latter produces the following queries: 253 | 254 | ```sql 255 | select `post`.* from `post`; 256 | select `tag`.*, `post_tag`.`post_id` as `_pivot_post_id`, `post_tag`.`tag_id` as `_pivot_tag_id` from `tag` inner join `post_tag` on `post_tag`.`tag_id` = `tag`.`id` where `post_tag`.`post_id` in (1); 257 | select `tag`.*, `post_tag`.`post_id` as `_pivot_post_id`, `post_tag`.`tag_id` as `_pivot_tag_id` from `tag` inner join `post_tag` on `post_tag`.`tag_id` = `tag`.`id` where `post_tag`.`post_id` in (2); 258 | select `tag`.*, `post_tag`.`post_id` as `_pivot_post_id`, `post_tag`.`tag_id` as `_pivot_tag_id` from `tag` inner join `post_tag` on `post_tag`.`tag_id` = `tag`.`id` where `post_tag`.`post_id` in (3); 259 | select `tag`.*, `post_tag`.`post_id` as `_pivot_post_id`, `post_tag`.`tag_id` as `_pivot_tag_id` from `tag` inner join `post_tag` on `post_tag`.`tag_id` = `tag`.`id` where `post_tag`.`post_id` in (4); 260 | ``` 261 | 262 | ## Using DataLoader to batch queries 263 | 264 | [DataLoader](https://github.com/facebook/dataloader) is used to create a data loader. `DataLoader` is constructed using a batch loading function. A batch loading function accepts an array of keys, and returns a promise which resolves to an array of values. 265 | 266 | Use the resulting data loader function to load values. DataLoader will coalesce all individual loads which occur within a single tick of an event loop and then call your batch loading function. 267 | 268 | This definition of the DataLoader is taken more or less verbatim from the [documentation](https://github.com/facebook/dataloader#getting-started). It sounds cool, but it didn't make much sense to me. 269 | 270 | Lets use DataLoader to fix the N+1 problem in the blog example. 271 | 272 | First, I need a function that can load a batch of tags in one query. 273 | 274 | ```js 275 | const getPostTagsUsingPostId = (postIds) => { 276 | return Post 277 | .collection(postIds.map((id) => { 278 | return { 279 | id 280 | }; 281 | })) 282 | .load('tags') 283 | .call('toJSON') 284 | .then((collection) => { 285 | // Bookshelf 0.10.0 uses Bluebird ^2.9.4. 286 | // Support for .mapSeries has been added in Bluebird v3. 287 | return collection.map((post) => { 288 | return post.tags; 289 | }); 290 | }); 291 | }; 292 | ``` 293 | 294 | `getPostTagsUsingPostId` will construct a single query to fetch tags for a list of post IDs, e.g. 295 | 296 | ```sql 297 | select 298 | `tag`.*, 299 | `post_tag`.`post_id` as `_pivot_post_id`, 300 | `post_tag`.`tag_id` as `_pivot_tag_id` 301 | from 302 | `tag` 303 | inner join 304 | `post_tag` 305 | on 306 | `post_tag`.`tag_id` = `tag`.`id` 307 | where 308 | `post_tag`.`post_id` in (?) 309 | ``` 310 | 311 | Second, I need to create a data loader function: 312 | 313 | ```js 314 | import DataLoader from 'dataloader'; 315 | 316 | const TagByPostIdLoader = new DataLoader(getPostTagsUsingPostId); 317 | ``` 318 | 319 | Finally, I need `PostType` to use the [`.load()`](https://github.com/facebook/dataloader#loadkey) function of the resulting data loader object to resolve data: 320 | 321 | ```js 322 | const PostType = new GraphQLObjectType({ 323 | name: 'Post', 324 | fields: () => { 325 | return { 326 | id: { 327 | type: new GraphQLNonNull(GraphQLID) 328 | }, 329 | name: { 330 | type: GraphQLString 331 | }, 332 | tags: { 333 | type: new GraphQLList(TagType), 334 | resolve: (post) => { 335 | return TagByPostIdLoader.load(post.id); 336 | } 337 | } 338 | }; 339 | } 340 | }); 341 | ``` 342 | 343 | Now, lets rerun the earlier query: 344 | 345 | ```js 346 | const query = `{ 347 | posts { 348 | id, 349 | name, 350 | tags { 351 | id, 352 | name 353 | } 354 | } 355 | }`; 356 | 357 | graphql(schema, query) 358 | ``` 359 | 360 | This time, we have fetched the data using just two queries: 361 | 362 | ```sql 363 | select `post`.* from `post` 364 | select `tag`.*, `post_tag`.`post_id` as `_pivot_post_id`, `post_tag`.`tag_id` as `_pivot_tag_id` from `tag` inner join `post_tag` on `post_tag`.`tag_id` = `tag`.`id` where `post_tag`.`post_id` in (1, 2, 3, 4) 365 | ``` 366 | 367 | ### How does it work? 368 | 369 | Hopefully, now the earlier description makes more sense: 370 | 371 | > DataLoader is used to create a data loader. `DataLoader` is constructed using a batch loading function. A batch loading function accepts an array of keys, and returns a promise which resolves to an array of values. 372 | > 373 | > Use the resulting data loader function to load values. DataLoader will coalesce all individual loads which occur within a single tick of an event loop and then call your batch loading function. 374 | 375 | If you are still struggling, I have made an interactive example: 376 | 377 | ```js 378 | // tonic ^6.0.0 379 | const DataLoader = require('dataloader'); 380 | 381 | const getPostTagsUsingPostId = (ids) => { 382 | console.log(ids); 383 | 384 | return Promise.resolve(ids); 385 | }; 386 | 387 | const TagByPostIdLoader = new DataLoader(getPostTagsUsingPostId); 388 | 389 | TagByPostIdLoader.load(1); 390 | TagByPostIdLoader.load(2); 391 | TagByPostIdLoader.load(3); 392 | 393 | // Force next-tick 394 | setTimeout(() => { 395 | TagByPostIdLoader.load(4); 396 | TagByPostIdLoader.load(5); 397 | TagByPostIdLoader.load(6); 398 | }, 100); 399 | 400 | // Force next-tick 401 | setTimeout(() => { 402 | TagByPostIdLoader.load(7); 403 | TagByPostIdLoader.load(8); 404 | TagByPostIdLoader.load(9); 405 | }, 200); 406 | 407 | setTimeout(() => { 408 | TagByPostIdLoader.load(10); 409 | TagByPostIdLoader.load(11); 410 | TagByPostIdLoader.load(12); 411 | }, 200); 412 | ``` 413 | 414 | ## To sum up 415 | 416 | [DataLoader](https://github.com/facebook/dataloader) allows you to decouple unrelated parts of your application without sacrificing the performance of batch data-loading. While the loader presents an API that loads individual values, all concurrent requests will be coalesced and presented to your batch loading function. This allows your application to safely distribute data fetching requirements throughout your application and maintain minimal outgoing data requests.[^https://github.com/facebook/dataloader/blame/68a2a2e9a347ff2acc35244ae29995ab625b2075/README.md#L88] 417 | 418 | I told you – it is magic. 419 | 420 | ## Further reading 421 | 422 | I thoroughly recommend reading the [source code of the DataLoader](https://github.com/facebook/dataloader/blob/master/src/index.js) (less than 300 lines of code). Ignoring the cache logic, the [underlying implementation](https://github.com/facebook/dataloader/blob/68a2a2e9a347ff2acc35244ae29995ab625b2075/src/index.js#L206-L211) is a simple queue that is using [`process.nextTick`](https://nodejs.org/api/process.html#process_process_nexttick_callback_arg) to resolve an array of promises. Yet, it is a genius application. 423 | 424 | Finally, know that each `DataLoader` instance represents a unique cache. After being loaded once, the resulting value is cached, eliminating the redundant requests. You can leverage this to create a cache persisting throughout the life-time of the application, or create a new instance per each request. Continue to read about [DataLoader caching](https://github.com/facebook/dataloader#caching) to learn about cache invalidation. 425 | -------------------------------------------------------------------------------- /posts/using-mysql-in-node-js/index.md: -------------------------------------------------------------------------------- 1 | The purpose of this article is to introduce the practices that I have adopted over the years of using Node.js and MySQL. 2 | 3 | For all examples, I am using [`mysql`](https://github.com/felixge/node-mysql) package. 4 | 5 | ## Promisify MySQL 6 | 7 | I am using [`Promise.promisifyAll`](http://bluebirdjs.com/docs/api/promise.promisifyall.html) to create an async equivalent of each function declared in a prototype of `mysql/lib/Connection` and `mysql/lib/Pool`. 8 | 9 | ```js 10 | const mysql = require('mysql'); 11 | const Connection = require('mysql/lib/Connection'); 12 | const Pool = require('mysql/lib/Pool'); 13 | const Promise = require('bluebird'); 14 | 15 | Promise.promisifyAll([ 16 | Connection, 17 | Pool 18 | ]); 19 | ``` 20 | 21 | `Promise.promisifyAll` does not modify the existing methods. For every method in the target function prototype, `Promise.promisifyAll` creates a new method affixed with `Async` ending, e.g. [`Connection.prototype.query`](https://github.com/felixge/node-mysql/blob/1720920f7afc660d37430c35c7128b20f77735e3/lib/Connection.js#L189) method remains as it is. A new method `Connection.prototype.queryAsync` is added to the `Connection.prototype`. Invoking `Connection.prototype.queryAsync` will return a promise whose fate is decided by the callback behavior of the `Connection.prototype.query` function. 22 | 23 | ## Creating a Connection 24 | 25 | It is [recommended](https://github.com/felixge/node-mysql/tree/1720920f7afc660d37430c35c7128b20f77735e3#establishing-connections) to establish an explicit connection, e.g. 26 | 27 | ```js 28 | const connection = mysql 29 | .createConnection({ 30 | host: '127.0.0.1' 31 | }); 32 | 33 | connection 34 | .connect((error) => { 35 | if (error) { 36 | console.error('Connection error.', error); 37 | 38 | return; 39 | } 40 | 41 | console.log('Established connection.', connection.threadId); 42 | }); 43 | ``` 44 | 45 | Unlike implicit connection, using `connection.connect` allows to catch connection errors early in the program execution. 46 | 47 | Since we have promisified `Connection.prototype`, we can use `connection.connectAsync` method to handle connection as a promise: 48 | 49 | ```js 50 | connection 51 | .connectAsync() 52 | .then((result) => { 53 | console.log('Established connection.', connection.threadId); 54 | }) 55 | .catch((error) => { 56 | console.error('Connection error.', error); 57 | }); 58 | ``` 59 | 60 | ### Creating a Connection for an Operation 61 | 62 | If your program does most of the work without involving the database, then it is reasonable to open the database connection only when program logic requires it. The ideal scenario is: open a connection for a series of database tasks and close the connection when all tasks are completed. 63 | 64 | Meet [`Promise.using`](http://bluebirdjs.com/docs/api/promise.using.html): 65 | 66 | > In conjunction with [`.disposer`](http://bluebirdjs.com/docs/api/disposer.html), `using` will make sure that no matter what, the specified disposer will be called when the promise returned by the callback passed to `using` has settled. The disposer is necessary because there is no standard interface in node for disposing resources. 67 | 68 | 69 | ```js 70 | const createConnection = (connectionOptions) => { 71 | const connection = mysql.createConnection(connectionOptions); 72 | 73 | return connection 74 | .connectAsync() 75 | .then((result) => { 76 | return connection; 77 | }) 78 | .disposer(() => { 79 | return connection.endAsync(); 80 | }); 81 | }; 82 | ``` 83 | 84 | Keep in mind that `connect` resolves with a value of connection status, e.g. if connection is successful, `result` is equivalent to: 85 | 86 | ```js 87 | OkPacket { 88 | fieldCount: 0, 89 | affectedRows: 0, 90 | insertId: 0, 91 | serverStatus: 2, 92 | warningCount: 0, 93 | message: '', 94 | protocol41: true, 95 | changedRows: 0 96 | } 97 | ``` 98 | 99 | After establishing the connection, use the original `connection` object (constructed using `createConnection`) to make queries. 100 | 101 | I have created a helper function `createConnection` that (1) creates a connection using the provided `connectionOptions` object and (2) ends connection upon invocation of [`disposer`](http://bluebirdjs.com/docs/api/disposer.html) method. 102 | 103 | This is how you use `createConnection` with `Promise.using`: 104 | 105 | ```js 106 | Promise 107 | .using(createConnection({ 108 | host: '127.0.0.1' 109 | }), (connection) => { 110 | return Promise 111 | .all([ 112 | connection.queryAsync('SELECT 1'), 113 | connection.queryAsync('SELECT 2'), 114 | connection.queryAsync('SELECT 3'), 115 | ]); 116 | }); 117 | ``` 118 | 119 | The `disposer` callback is invoked as soon as all 3 queries are executed. All 3 queries share the same connection. 120 | 121 | ### Creating a Connection Pool 122 | 123 | If your program relies on constant connection to the database (e.g. an API endpoint that fetches data from a database), then it is desirable to have a [connection pool](https://en.wikipedia.org/wiki/Connection_pool): 124 | 125 | > Connection pools are used to enhance the performance of executing commands on a database. Opening and maintaining a database connection for each user, especially requests made to a dynamic database-driven website application, is costly and wastes resources. In connection pooling, after a connection is created, it is placed in the pool and it is used again so that a new connection does not have to be established. If all the connections are being used, a new connection is made and is added to the pool. Connection pooling also cuts down on the amount of time a user must wait to establish a connection to the database. 126 | 127 | Fortunately, abstracting connection pooling is similar to abstracting `mysql.createConnection`: 128 | 129 | ```js 130 | const pool = mysql 131 | .createPool({ 132 | host: '127.0.0.1' 133 | }); 134 | 135 | const getConnection = (pool) => { 136 | return pool 137 | .getConnectionAsync() 138 | .then((connection) => { 139 | return connection; 140 | }) 141 | .disposer((connection) => { 142 | return connection.releaseAsync(); 143 | }); 144 | }; 145 | ``` 146 | 147 | Note the subtle differences: 148 | 149 | * `connection.releaseAsync` is used instead of `connection.endAsync`. 150 | * `pool.getConnectionAsync` is used instead of `connection.connectAsync`. 151 | 152 | This is how you use `getConnection` with `Promise.using`: 153 | 154 | ```js 155 | Promise 156 | .using(getConnection(pool), (connection) => { 157 | return Promise 158 | .all([ 159 | connection.queryAsync('SELECT 1'), 160 | connection.queryAsync('SELECT 2'), 161 | connection.queryAsync('SELECT 3'), 162 | ]); 163 | }); 164 | ``` 165 | 166 | ## Leveraging Bluebird API 167 | 168 | In all of the examples, I have used `Bluebird.promisifyAll` to abstract [`mysql`](https://github.com/felixge/node-mysql) API. This means that all promises have access to the [Bluebird API](http://bluebirdjs.com/docs/api-reference.html). Keep that in mind when handling queries, e.g. use [`.spread`](http://bluebirdjs.com/docs/api/spread.html) method to get the first result from the array (`query` result is always an array): 169 | 170 | ```js 171 | connection 172 | .query('SELECT 1') 173 | .spread((id) => { 174 | assert(id === 1); 175 | }); 176 | ``` 177 | 178 | ## Reducing Nesting using Async Functions 179 | 180 | In general, promises are great for preventing the [callback hell](http://callbackhell.com/). However, consider an example where you have a series of queries that depend on the result of a previous query: 181 | 182 | ```js 183 | const getUserByEmail = (connection, userEmail) => { 184 | return connection 185 | .queryAsync('SELECT `id` FROM `user` WHERE `email` = ?', [ 186 | userEmail 187 | ]) 188 | .spread((user) => { 189 | if (!user) { 190 | return null; 191 | } 192 | 193 | return connection 194 | .queryAsync('SELECT `id`, `name` FROM `permission` WHERE `user_id` = ?', [ 195 | user.id 196 | ]) 197 | .then((permissions) => { 198 | return { 199 | ...user, 200 | permissions: permissions 201 | } 202 | }); 203 | }); 204 | }; 205 | ``` 206 | 207 | Using [Async Functions](https://github.com/tc39/ecmascript-asyncawait) we can flatten the dependency structure using `await` keyword, e.g. 208 | 209 | ```js 210 | const getUserByEmail = async (connection, userEmail) => { 211 | const user = await connection 212 | .queryAsync('SELECT `id` FROM `user` WHERE `email` = ?', [ 213 | userEmail 214 | ]) 215 | .then(_.head); 216 | 217 | if (!user) { 218 | return null; 219 | } 220 | 221 | const permissions = await connection 222 | .queryAsync('SELECT `id`, `name` FROM `permission` WHERE `user_id` = ?', [ 223 | user.id 224 | ]); 225 | 226 | return { 227 | ...user, 228 | permissions: permissions 229 | }; 230 | }; 231 | ``` 232 | 233 | Note: [`_.head`](https://lodash.com/docs#head) is a [lodash](https://lodash.com/) function. 234 | 235 | ## Named Placeholders 236 | 237 | `mysql` module uses `?` characters as placeholders for values, e.g. 238 | 239 | ```sql 240 | connection 241 | .query('SELECT ?, ?', [ 242 | 'foo', 243 | 'bar' 244 | ]); 245 | ``` 246 | 247 | This example is equivalent to: 248 | 249 | ```sql 250 | connection.query('SELECT ' + connection.escape('foo') + ', ' + connection.escape('bar')); 251 | ``` 252 | 253 | However, this approach becomes hard to read as query becomes large and the list of values long. 254 | 255 | There is an alternative: named placeholders. 256 | 257 | ```sql 258 | connection 259 | .query('SELECT :foo, :bar', { 260 | foo: 'FOO', 261 | bar: 'BAR' 262 | }); 263 | ``` 264 | 265 | The latter is equivalent to: 266 | 267 | ```sql 268 | connection.query('SELECT ' + connection.escape('FOO') + ', ' + connection.escape('BAR')); 269 | ``` 270 | 271 | Placeholder names can appear multiple times, e.g. 272 | 273 | ```sql 274 | connection 275 | .query('SELECT :foo, :foo', { 276 | foo: 'FOO' 277 | }); 278 | ``` 279 | 280 | The latter is equivalent to: 281 | 282 | ```sql 283 | connection.query('SELECT ' + connection.escape('FOO') + ', ' + connection.escape('FOO')); 284 | ``` 285 | 286 | As of this writing, `mysql` [does not support named parameters](https://github.com/felixge/node-mysql/issues/920). 287 | 288 | However, it is easy to patch `Connection.prototype.query` prototype to add the support: 289 | 290 | First, you need to install [`named-placeholders`](https://www.npmjs.com/package/named-placeholders) package. 291 | 292 | Then, patch the `Connection.prototype.query`: 293 | 294 | ```js 295 | const toUnnamed = require('named-placeholders')(); 296 | const originalQuery = require('mysql/lib/Connection').prototype.query; 297 | 298 | require('mysql/lib/Connection').prototype.query = function (...args) { 299 | if (Array.isArray(args[0]) || !args[1]) { 300 | return originalQuery.apply(this, args); 301 | } 302 | 303 | ([ 304 | args[0], 305 | args[1] 306 | ] = toUnnamed(args[0], args[1])); 307 | 308 | return originalQuery.apply(this, args); 309 | }; 310 | ``` 311 | 312 | That's it. You can now use named placeholders. 313 | 314 | ## Constructing Queries 315 | 316 | There are several problems associated with constructing queries. 317 | 318 | ### Queries That Span Multiple Lines 319 | 320 | The problem with writing queries that span multiple lines is simple: JavaScript does not support strings that span multiple lines. The closest thing to multi-line string support is [template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals). The problem with using a template literal to declare a MySQL query is that their syntaxes are incompatible: 321 | 322 | * Template literals are [enclosed by the back-tick (`` ` ``)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Syntax) character; and 323 | * MySQL uses the backtick character to [quote the identifiers](http://dev.mysql.com/doc/refman/5.7/en/identifiers.html). 324 | 325 | For this reason, I store each query in a dedicated file. The added benefit of having SQL queries in a dedicated file is: 326 | 327 | * Makes it easy to track changes in the version control. 328 | * Separates code development from query writing. 329 | * Enforces statically typed queries (prohibits inline conditions). 330 | * You can load SQL queries in a dedicated IDE. 331 | 332 | Tip: To use queries defined in a separate file, create a helper function that loads the query and caches the result. 333 | 334 | ### Dynamic Expressions 335 | 336 | First of all, I avoid use of dynamic expressions whenever possible. Reason: when queries are predictable, DBAs can optimize indexes. When you need to query data based on dynamic criteria, consider using a search server (e.g. https://www.elastic.co/). 337 | 338 | However, there are cases (e.g. building a prototype product) when you will want to dynamically build queries simply because it is less complex than the alternatives. 339 | 340 | In this case, I suggest using a query builder (e.g. [Squel.js](https://hiddentao.github.io/squel)) to build expressions. 341 | 342 | Note: I do not recommend building queries using a query builder. A common pro-query builder argument is that abstracting SQL code using a query builder makes it easy to migrate from one database engine to another, e.g. from MySQL to PostgresQL. This might be the case, but you got to ask yourself – how many times in your professional career did you need to migrate a project from one database to another database without rewriting most of the underlying code regardless of whether you were using a query builder/ ORM or not. Probably not that many. 343 | 344 | Lets consider a simple example: 345 | 346 | ```sql 347 | CREATE TABLE `person` ( 348 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 349 | `first_name` varchar(255) DEFAULT NULL, 350 | `last_name` varchar(255) DEFAULT NULL, 351 | `email` varchar(255) DEFAULT NULL, 352 | PRIMARY KEY (`id`), 353 | UNIQUE KEY `email` (`email`) 354 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 355 | ``` 356 | 357 | A database table storing people data. The requirement: user must be able to filter data using any of the `person` defining columns. 358 | 359 | Start by writing a query to get all the relevant records: 360 | 361 | ```sql 362 | SELECT 363 | `id`, 364 | `first_name`, 365 | `last_name`, 366 | `email` 367 | FROM 368 | `person` 369 | WHERE 370 | 1 /* expression */ 371 | ``` 372 | 373 | `WHERE 1` forces to return all results. 374 | 375 | Now lets build an expression using [`squel.expr`](http://hiddentao.github.io/squel/#expressions) to filter only the relevant records: 376 | 377 | ```js 378 | const squel = require('squel'); 379 | 380 | const expression = squel.expr(); 381 | 382 | if (userQuery.firstName) { 383 | expression.and('`first_name` LIKE ?', userQuery.firstName); 384 | } 385 | 386 | if (userQuery.lastName) { 387 | expression.and('`last_name` LIKE ?', userQuery.lastName); 388 | } 389 | 390 | if (userQuery.email) { 391 | expression.and('`email` LIKE ?', userQuery.email); 392 | } 393 | ``` 394 | 395 | Use `squel.expr().toString()` to convert query object to SQL expression (or an empty string if query is empty). Use `String.prototype.replace` to replace `1 /* expression */` with the generated expression or `1` (`WHERE` clause requires a specifier). 396 | 397 | ```js 398 | const fs = require('fs'); 399 | const selectPersonsQuery = fs.readFileSync('./selectPersonsQuery.sql', 'utf8'); 400 | 401 | const whereExpression = expression.toString(); 402 | 403 | const generatedSelectPersonsQuery = selectPersonsQuery.replace('1 /* expression */', whereExpression || 1); 404 | 405 | connection.query(generatedSelectPersonsQuery); 406 | ``` 407 | 408 | Done. You have a working query builder. 409 | 410 | This approach allows utilization of query builder without sacrificing the readability of the query body. 411 | 412 | ## Debugging 413 | 414 | `mysql` package documentation is a lengthy, monolithic document. As a result, it is not a surprise that the [section about debugging](https://github.com/felixge/node-mysql/tree/1720920f7afc660d37430c35c7128b20f77735e3#debugging-and-reporting-problems) is often overlooked. 415 | 416 | Debugging is enabled at the time of configuring the connection, e.g. 417 | 418 | ```js 419 | mysql 420 | .createConnection({ 421 | debug: true 422 | }); 423 | ``` 424 | 425 | Enabling debugging will print all outgoing and incoming packets on stdout. You can filter the log by the packet type: 426 | 427 | ```js 428 | mysql 429 | .createConnection({ 430 | debug: [ 431 | 'ComQueryPacket', 432 | 'RowDataPacket' 433 | ] 434 | }); 435 | ``` 436 | 437 | This configuration will log only the queries being sent and the result being returned, e.g. 438 | 439 | ``` 440 | --> ComQueryPacket 441 | ComQueryPacket { command: 3, sql: 'SELECT \'A\', \'B\'' } 442 | 443 | <-- RowDataPacket 444 | RowDataPacket { A: 'A', B: 'B' } 445 | ``` 446 | 447 | ## `mysql2` 448 | 449 | [`mysql2`](https://github.com/sidorares/node-mysql2) is an alternative MySQL driver for Node.js. It is [mostly](https://github.com/sidorares/node-mysql2/tree/cd16a0a7ad2d273fa5126135479d4698bd554cea#known-incompatibilities-with-node-mysql) compatible with the `mysql` driver. 450 | 451 | Notable differences between `mysql2` and `mysql` are: 452 | 453 | * `mysql2` has a considerable performance advantage. 454 | * `mysql2` natively supports named placeholders[^https://github.com/sidorares/node-mysql2#named-placeholders]. 455 | * `mysql2` supports true prepared statements[^https://github.com/sidorares/node-mysql2#prepared-statements]. `mysql` implementation imitates[^https://github.com/felixge/node-mysql/tree/1720920f7afc660d37430c35c7128b20f77735e3#escaping-query-values] prepared statements by escaping the values and executing the query with interpolated values. 456 | 457 | In my eyes, the only downside to using `mysql2` over `mysql` is its relatively small community: 458 | 459 | ||`mysql`|`mysql2`| 460 | |---|---|---| 461 | |GitHub Stargazers|6695|284| 462 | |GitHub Forks|1087|57| 463 | |Github Watchers|378|30| 464 | |NPM downloads last month|462,781|11,701| 465 | 466 | Last updated: Tue Apr 26 20:53:20 2016. 467 | 468 | ### Performance 469 | 470 | `mysql2` boosts considerable performance improvements over `mysql`. Here is a summary of the [benchmark](https://gist.github.com/sidorares/ffe9ee9c423f763e3b6b) that `mysql2` is using to prove its edge: 471 | 472 | * `node-mysql`: peaks 6000 rps, first timeout errors at 110 conns, latency99 60ms at 110 conns 473 | * `node-mysql2`: peak 15000 rps, first timeout errors at 150 conns, latency99 30ms at 150 conn 474 | * `memory`: peak 46000 rps, no erros, latency99 20ms at 300 conns (7ms at 120 conn) 475 | --------------------------------------------------------------------------------