├── .gitignore ├── .editorconfig ├── spec.html ├── package.json ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── HISTORY.md ├── CONTRIBUTING.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | out 4 | dist 5 | npm-debug.log 6 | deploy_key 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | charset = utf-8 8 | indent_style = space 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /spec.html: -------------------------------------------------------------------------------- 1 |
 2 | title: ES Function helpers (2021)
 3 | status: proposal
 4 | stage: 0
 5 | location: https://github.com/js-choi/proposal-function-helpers
 6 | copyright: false
 7 | contributors: J. S. Choi
 8 | 
9 | 10 | 11 | 12 | 13 |

Introduction

14 |
15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "proposal-function-helpers", 4 | "description": "Draft specification for several Function helpers in JavaScript.", 5 | "author": "J. S. Choi (https://jschoi.org/)", 6 | "license": "BSD-3-Clause", 7 | "repository": "github:js-choi/proposal-function-helpers", 8 | "scripts": { 9 | "prespec": "mkdir -p out/", 10 | "spec": "ecmarkup spec.html out/index.html --assets inline --contributors \"J. S. Choi, Ecma International\" --copyright true --title \"ES Function helpers (2021)\" --status \"proposal\" --stage 0", 11 | "prewatch": "npm run prespec", 12 | "watch": "npm run spec -- --watch" 13 | }, 14 | "devDependencies": { 15 | "ecmarkup": "^8.0.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of conduct 2 | This repository is a TC39 project, and it therefore subscribes to 3 | its [code of conduct][CoC]. It is available at . 4 | 5 | We all should strive here to be respectful, friendly and patient, 6 | inclusive, considerate, and careful in the words we choose. 7 | When we disagree, we should try to understand why. 8 | 9 | To ask a question or report an issue, please follow the [CoC]’s directions, e.g., emailing 10 | [tc39-conduct-reports@googlegroups.com][]. 11 | 12 | More information about contributing is also available in [CONTRIBUTING.md][]. 13 | 14 | [CoC]: https://tc39.es/code-of-conduct/ 15 | [tc39-conduct-reports@googlegroups.com]: mailto:tc39-conduct-reports@googlegroups.com 16 | [CONTRIBUTING.md]: https://github.com/tc39/proposal-pipeline-operator/blob/main/CONTRIBUTING.md 17 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2021 J. S. Choi 2 | 3 | Redistribution and use in source and binary forms, 4 | with or without modification, are permitted 5 | provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its contributors 15 | may be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | **This software is provided by the copyright holders and contributors “as is” 19 | and any express or implied warranties, including, but not limited to, 20 | the implied warranties of merchantability and fitness for a particular purpose 21 | are disclaimed. 22 | In no event shall the copyright holder or contributors be liable 23 | for any direct, indirect, incidental, special, exemplary, or consequential damages 24 | (including, but not limited to, procurement of substitute goods or services; 25 | loss of use, data, or profits; or business interruption) 26 | however caused and on any theory of liability, 27 | whether in contract, strict liability, or tort (including negligence or otherwise) 28 | arising in any way out of the use of this software, 29 | even if advised of the possibility of such damage.** 30 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # Brief history of JavaScript function helpers 2 | Function operations in JavaScript has a long and twisty history. 3 | That history can give essential context behind this proposal. 4 | 5 | For information on what “Stage 1” and “Stage 2” mean, 6 | read about the [TC39 Process][]. 7 | 8 | More information about contributing is also available in [CONTRIBUTING.md][]. 9 | 10 | ## 2015–2021 11 | The pipe champion group presents F# pipes (a tacit-unary-function-application 12 | operator) for Stage 2 twice to TC39, being unsuccessful both times due to 13 | pushback from multiple other TC39 representatives’ memory performance concerns, 14 | syntax concerns about await, and concerns about encouraging ecosystem 15 | bifurcation/forking. 16 | 17 | For more information, see the [pipe proposal’s HISTORY.md][pipe history]. 18 | 19 | ## 2021-09 20 | Inspired by [pipe issue #233][], [@js-choi][] creates a new proposal 21 | that would add several Function helper methods. 22 | 23 | ## 2021-10 24 | [On 2021-10, proposal-function-helpers is presented to the Committee 25 | plenary][2021-10] for Stage 1. The Committee rejects the proposal due to its 26 | being overly broad and requests that it be split up into multiple proposals. 27 | 28 | [TC39 process]: https://tc39.es/process-document/ 29 | [CONTRIBUTING.md]: https://github.com/tc39/proposal-pipeline-operator/blob/main/CONTRIBUTING.md 30 | 31 | [pipe history]: https://github.com/tc39/proposal-pipeline-operator/blob/main/HISTORY.md 32 | [pipe issue #233]: https://github.com/tc39/proposal-pipeline-operator/issues/233 33 | 34 | [@js-choi]: https://github.com/js-choi 35 | 36 | [2021-10]: https://github.com/tc39-transfer/proposal-function-helpers/issues/17#issuecomment-953814353 37 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the ES pipe operator’s proposal 2 | First off, thank you for taking the time to contribute! 🎉 3 | 4 | Here are some suggestions to contributing to this proposal. 5 | The pipe operator in JavaScript has a [long and twisty history][HISTORY.md]. 6 | A lot of issues have already been argued round in round in circles. 7 | 8 | Bearing that in mind, please try to read the following 9 | before making new issues or comments: 10 | 11 | 1. [HISTORY.md][]: This will give a lot of context 12 | behind what’s been happening to the proposal since its creation in 2015. 13 | 2. The general [TC39 Process][], which summarizes 14 | how TC39’s “consensus” and “Stages” work. 15 | 3. The guide on [contributing to TC39 proposals][contributing guide]. 16 | 4. The [TC39 Code of Conduct][CoC]: 17 | It has important information about how we’re all expected to act 18 | and what to do when we feel like someone’s conduct does not meet the Code. 19 | We all want to maintain a friendly, productive working environment! 20 | 5. The [TC39 How to Give Feedback][feedback] article. 21 | 6. The [proposal explainer][] to make sure that it is 22 | not already addressed there. 23 | 7. The [TC39 Matrix guide][] (if you want to chat with TC39 members on Matrix, 24 | which is a real-time chat platform). 25 | 26 | 8. If the explainer does not already explain your topic adequately, 27 | then please [search the GitHub repository’s issues][issues] 28 | to see if any issues match the topic you had in mind. 29 | This proposal is more than four years old, 30 | and it is likely that the topic has already been raised and thoroughly discussed. 31 | 32 | You can leave a comment on an [existing GitHub issue][issues], 33 | create a new issue (but really do try to [find an existing GitHub issue][issues] first), 34 | or [participate on Matrix][TC39 Matrix guide]. 35 | 36 | Please try to keep any existing GitHub issues on their original topic. 37 | 38 | If you feel that someone’s conduct is not meeting the [TC39 Code of Conduct][CoC], 39 | whether in this GitHub repository or in a [TC39 Matrix room][TC39 Matrix guide], 40 | then please follow the [Code of Conduct][CoC]’s directions for reporting the violation, 41 | including emailing [tc39-conduct-reports@googlegroups.com][]. 42 | 43 | Thank you again for taking the time to contribute! 44 | 45 | [HISTORY.md]: https://github.com/tc39/proposal-pipeline-operator/blob/main/HISTORY.md 46 | [CoC]: https://tc39.es/code-of-conduct/ 47 | [TC39 process]: https://tc39.es/process-document/ 48 | [contributing guide]: https://github.com/tc39/ecma262/blob/master/CONTRIBUTING.md#new-feature-proposals 49 | [feedback]: https://github.com/tc39/how-we-work/blob/master/feedback.md 50 | [proposal explainer]: https://github.com/tc39/proposal-pipeline-operator/blob/main/README.md 51 | [TC39 Matrix guide]: https://github.com/tc39/how-we-work/blob/master/matrix-guide.md 52 | [issues]: https://github.com/tc39/proposal-pipeline-operator/issues?q=is%3Aissue+ 53 | [tc39-conduct-reports@googlegroups.com]: mailto:tc39-conduct-reports@googlegroups.com 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Function helpers for JavaScript 2 | Withdrawn ECMAScript Stage-0 Proposal. J. S. Choi, 2021. 3 | 4 | [On 2021-10, proposal-function-helpers was presented to the Committee 5 | plenary][2021-10] for Stage 1. The Committee rejected the proposal due to its 6 | being overly broad and requests that it be split up into multiple proposals. 7 | This proposal is therefore **withdrawn** and split into 8 | [proposal-function-pipe-flow][] and [proposal-function-un-this][]. 9 | 10 | [2021-10]: https://github.com/tc39-transfer/proposal-function-helpers/issues/17#issuecomment-953814353 11 | [proposal-function-pipe-flow]: https://github.com/js-choi/proposal-function-pipe-flow 12 | [proposal-function-un-this]: https://github.com/js-choi/proposal-function-un-this 13 | 14 | * [Proposal history][HISTORY.md] 15 | 16 | [HISTORY.md]: https://github.com/js-choi/proposal-function-helpers/blob/main/HISTORY.md 17 | 18 |
19 | 20 | Original proposal 21 | 22 | Several useful, common helper functions are defined, downloaded, and used a lot. 23 | We should standardize at least some of them. 24 | This proposal is seeking Committee consensus for Stage 1: 25 | that standardizing at least some Function helpers is “worth investigating”. 26 | It is not seeking to standardize every imaginable helper function: 27 | just a selected few frequently used functions. 28 | Choosing which functions to standardize would be bikeshedding for Stage 2. 29 | Alternatively, the Committee could request that this proposal 30 | be split up into multiple proposals. 31 | 32 | These convenience functions are simple, 33 | and they can be reimplemented easily in userspace. 34 | So why standardize them? Because: 35 | 36 | 1. These helper functions are commonly used and universally useful. 37 | Each function is frequently downloaded from NPM, 38 | despite their being easily reimplementable. 39 | This is to be expected: 40 | after all, every JavaScript developer needs to manipulate callbacks, 41 | but they often do not wish to write the utilities themselves. 42 | 2. Standardization would improve developer ergonomics. 43 | If we find ourselves needing these functions in a REPL or script, 44 | instead of having to download an external package 45 | or pasting in a definition into our own code, 46 | we can simply destructure the `Function` object. 47 | 3. Standardization would improve code clarity. 48 | There would be one standard name for each of these functions, 49 | rather than various names from various libraries 50 | that refer to the same thing. 51 | 52 | Unlike new syntax, standardized helper functions are relatively lightweight ways 53 | to improve the experience of all developers. 54 | These helper functions are well-trodden cowpaths, 55 | each of which deserves consideration for standardization. 56 | 57 | The following functions are only possibilities. 58 | Choosing which functions to standardize would be bikeshedding for Stage 2. 59 | 60 | ## Function.flow 61 | The `Function.flow` static method creates a new function by combining several callbacks. 62 | 63 | ```js 64 | Function.flow(...fns); 65 | 66 | const { flow } = Function; 67 | 68 | const f = flow(f0, f1, f2); 69 | f(5, 7); // f2(f1(f0(5, 7))). 70 | 71 | const g = flow(g0); 72 | g(5, 7); // g0(5, 7). 73 | 74 | const h = flow(); 75 | h(5, 7); // 5. 76 | ``` 77 | 78 | The following real-world examples originally used [lodash.flow][]. 79 | 80 | ```js 81 | // From gatsby@3.14.3/packages/gatsby-plugin-sharp/src/plugin-options.js: 82 | flow( 83 | mapUserLinkHeaders(pluginData), 84 | applySecurityHeaders(pluginOptions), 85 | applyCachingHeaders(pluginData, pluginOptions), 86 | mapUserLinkAllPageHeaders(pluginData, pluginOptions), 87 | applyLinkHeaders(pluginData, pluginOptions), 88 | applyTransfromHeaders(pluginOptions), 89 | saveHeaders(pluginData) 90 | ) 91 | 92 | // From strapi@3.6.8 93 | // packages/strapi-admin/services/permission/permissions-manager/query-builers.js: 94 | const transform = flow(flattenDeep, cleanupUnwantedProperties); 95 | 96 | // From semantic-ui-react@v2.0.4/docs/static/utils/getInfoForSeeTags.js: 97 | const getInfoForSeeTags = flow( 98 | _.get('docblock.tags'), 99 | _.filter((tag) => tag.title === 'see'), 100 | _.map((tag) => { 101 | }), 102 | ) 103 | ``` 104 | 105 | Any function created by `Function.flow` 106 | applies its own arguments to its leftmost callback. 107 | Then that result is applied to its next callback. 108 | In other words, function composition occurs from left to right. 109 | 110 | The leftmost callback may have any arity, 111 | but any subsequent callbacks are expected to be unary. 112 | 113 | If `Function.flow` receives no arguments, then, by default, 114 | it will return `Function.identity` (which is defined later in this proposal). 115 | 116 | Precedents include: 117 | * [lodash][]: [lodash.flow][] is individually downloaded from NPM 118 | about [600,000 times weekly][lodash.flow] 119 | * [fp-ts][]: `import { flow } from 'fp-ts/function';` 120 | * [Ramda][]: `import { pipe } from 'ramda/src/pipe';` 121 | * [RxJS][]: `import { pipe } from 'rxjs';` 122 | 123 | [lodash.flow]: https://www.npmjs.com/package/lodash.flow 124 | 125 | ## Function.flowAsync 126 | The `Function.flowAsync` static method creates a new function 127 | by combining several potentially async callbacks; 128 | the created function will always return a promise. 129 | 130 | ```js 131 | Function.flowAsync(...fns); 132 | 133 | const { flowAsync } = Function; 134 | 135 | // (...args) => Promise.resolve(x).then(f0).then(f1).then(f2). 136 | flowAsync(f0, f1, f2); 137 | 138 | const f = flowAsync(f0, f1, f2); 139 | await f(5, 7); // await f2(await f1(await f0(5, 7))). 140 | 141 | const g = flowAsync(g0); 142 | await g(5, 7); // await g0(5, 7). 143 | 144 | const h = flowAsync(); 145 | await h(5, 7); // await 5. 146 | ``` 147 | 148 | Any function created by `Function.flowAsync` 149 | applies its own arguments to its leftmost callback. 150 | Then that result is `await`ed before being applied to its next callback. 151 | In other words, async function composition occurs from left to right. 152 | 153 | The leftmost callback may have any arity, 154 | but any subsequent callbacks are expected to be unary. 155 | 156 | If `Function.flowAsync` receives no arguments, then, by default, 157 | it will return `Promise.resolve`. 158 | 159 | The name “flow” comes from lodash.flow. 160 | (The name compose would be confusing with other languages’ RTL function composition.) 161 | 162 | ## Function.pipe 163 | The `Function.pipe` static method applies a sequence 164 | of callbacks to a given input value, returning the final callback’s result. 165 | 166 | ```js 167 | Function.pipe(input, ...fns); 168 | 169 | const { pipe } = Function; 170 | 171 | // f2(f1(f0(5))). 172 | pipe(5, f0, f1, f2); 173 | 174 | // 5. 175 | pipe(5); 176 | 177 | // undefined. 178 | pipe(); 179 | ``` 180 | 181 | The following real-world examples originally used [fp-ts][]’s `pipe` function. 182 | 183 | ```js 184 | // From @gripeless/pico@1.0.1/source/inline.ts: 185 | return pipe( 186 | download(absoluteURL), 187 | mapRej(downloadErrorToDetailedError), 188 | chainFluture(responseToBlob), 189 | chainFluture(blobToDataURL), 190 | mapFluture(dataURL => `url(${dataURL})`) 191 | ) 192 | 193 | // From StoplightIO Prism v4.5.0 packages/http/src/validator/validators/body.ts: 194 | return pipe( 195 | specs, 196 | A.findFirst(spec => !!typeIs(mediaType, [spec.mediaType])), 197 | O.alt(() => A.head(specs)), 198 | O.map(content => ({ mediaType, content })) 199 | ); 200 | ``` 201 | 202 | The first callback is applied to `input`, 203 | then the second callback is applied to the first callback’s result, 204 | and so forth. 205 | In other words, function piping occurs from left to right. 206 | 207 | Each callback is expected to be a unary function. 208 | 209 | If `Function.pipe` receives only one argument, then it will return `input` by default.\ 210 | If `Function.pipe` receives no arguments, then it will return `undefined`. 211 | 212 | Precedents include: 213 | * [fp-ts][]: `import { pipe } from 'fp-ts/function';` 214 | 215 | *** 216 | 217 |
218 | What happened to the F# pipe operator? 219 | 220 | *** 221 | 222 | F#, Haskell, and other languages that are based on auto-curried unary functions 223 | have a tacit-unary-function-application operator. 224 | The pipe champion group has presented F# pipes for Stage 2 twice to TC39, 225 | being unsuccessful both times 226 | due to pushback from multiple other TC39 representatives’ 227 | memory performance concerns, syntax concerns about await, 228 | and concerns about encouraging ecosystem bifurcation/forking. 229 | (For more information, see the [pipe proposal’s HISTORY.md][pipe history].) 230 | 231 | Given this reality, TC39 is much more likely to pass 232 | a `Function.pipe` helper function than a similar syntactic operator. 233 | 234 | Standardizing a helper function does not preclude 235 | standardizing an equivalent operator later. 236 | For example, TC39 standardized binary `**` even when `Math.pow` existed. 237 | 238 | In the future, we might try to propose a F# pipe operator, 239 | but we would like to try proposing `Function.pipe` first, 240 | in an effort to bring its benefits to the wider JavaScript community 241 | as soon as possible. 242 | 243 |
244 | 245 | ## Function.pipeAsync 246 | The `Function.pipeAsync` static method applies a sequence 247 | of potentially async callbacks to a given input value, returning a promise. 248 | The promise will resolve to the final callback’s result. 249 | 250 | ```js 251 | Function.pipeAsync(input, ...fns); 252 | 253 | const { pipeAsync } = Function; 254 | 255 | // Promise.resolve(5).then(f0).then(f1).then(f2). 256 | pipeAsync(5, f0, f1, f2); 257 | 258 | // Promise.resolve(5). 259 | pipeAsync(5); 260 | 261 | // Promise.resolve(undefined). 262 | pipeAsync(); 263 | ``` 264 | 265 | The input is first `await`ed. 266 | Then the first callback is applied to `input` and then `await`ed, 267 | then the second callback is applied to the first callback’s result then `await`ed, 268 | and so forth. 269 | In other words, function piping occurs from left to right. 270 | 271 | Each callback is expected to be a unary function. 272 | 273 | If any callback returns a promise that then rejects with an error, 274 | then the promise returned by `Function.pipeAsync` will reject with the same error. 275 | 276 | If `Function.pipeAsync` receives only one argument, 277 | then it will return `Promise.resolve(input)` by default.\ 278 | If `Function.pipeAsync` receives no arguments, 279 | then it will return `Promise.resolve(undefined)`. 280 | 281 | ## Function.constant 282 | The `Function.constant` static method creates a new function from a constant value. 283 | The new function will always return that value, no matter what arguments it is given. 284 | 285 | ```js 286 | Function.constant(value); 287 | 288 | const { constant } = Function; 289 | 290 | const f = constant(5); 291 | f(11, 0, 3); // 5. 292 | 293 | const g = constant(); 294 | g(11, 0, 3); // undefined. 295 | ``` 296 | 297 | The following real-world examples originally used [lodash.constant][]. 298 | 299 | ```js 300 | // From cypress@8.6.0/packages/net-stubbing/lib/server/util.ts: 301 | setDefaultHeader('access-control-expose-headers', constant('*')) 302 | 303 | // From cypress@8.6.0/packages/driver/src/cypress/utils.ts: 304 | return [fn, constant(type)] 305 | 306 | // From Odoo v15.0 addons/pad/static/src/js/pad.js: 307 | url.toJSON = constant(this.url); 308 | 309 | // From ng-table@3.0.1/test/specs/settings.spec.ts: 310 | const newSettings: = { 311 | filterOptions: _.mapValues(allSettings.filterOptions, constant(undefined)), 312 | dataOptions: _.mapValues(allSettings.dataOptions, constant(undefined)), 313 | groupOptions: _.mapValues(allSettings.groupOptions, constant(undefined)) 314 | }; 315 | 316 | // From Elastic Kibana v7.15.1 317 | // src/plugins/vis_types/vislib/public/fixtures/mock_data/histogram/_slices.js. 318 | { 319 | name: 0, 320 | size: 378611, 321 | aggConfig: { 322 | type: 'histogram', 323 | schema: 'segment', 324 | fieldFormatter: constant(String), 325 | params: { 326 | interval: 1000, 327 | extended_bounds: { /* … */ }, 328 | }, 329 | }, 330 | /* … */ 331 | }, 332 | 333 | // From Yhat Rodeo v2.5.2 src/node/services/files.test.js: 334 | fs.lstat.onCall(0).yields(null, {isDirectory: constant(true)}); 335 | ``` 336 | 337 | Precedents include: 338 | * [lodash][]: [lodash.constant][] is individually downloaded from NPM 339 | about [81,000 times weekly][lodash.constant] 340 | * [stdlib][]: `import constantFunction from '@stdlib/utils-constant-function';` 341 | * [fp-ts][]: `import { constant } from 'fp-ts/function';` 342 | * [Ramda][]: `import { always } from 'ramda/src/always';` 343 | 344 | [lodash.constant]: https://www.npmjs.com/package/lodash.constant 345 | 346 | ## Function.identity 347 | The `Function.identity` static method always returns its first argument. 348 | 349 | ```js 350 | Function.identity(value); 351 | 352 | const { identity } = Function; 353 | 354 | identity(5); // 5. 355 | identity(); // undefined. 356 | ``` 357 | 358 | The following real-world examples originally used [lodash.identity][]. 359 | 360 | ```js 361 | // From cypress@8.6.0/packages/driver/src/cypress/runner.ts: 362 | // “Iterates over a suite's tests (including nested suites) 363 | // and will return as soon as the callback is true”. 364 | const findTestInSuite = (suite, fn = identity) => { 365 | for (const test of suite.tests) { 366 | if (fn(test)) { 367 | return test 368 | } /* … */ 369 | } 370 | } 371 | 372 | // From gatsby@3.14.3/packages/gatsby-plugin-sharp/src/plugin-options.js: 373 | // “Get all non falsey values”. 374 | return _.pickBy(options, identity) 375 | 376 | // From gatsby@3.14.3/packages/gatsby-plugin-gatsby-cloud/src/constants.js: 377 | export const DEFAULT_OPTIONS = { 378 | // “Optional transform for manipulating headers for sorting, etc”. 379 | transformHeaders: identity, 380 | /* … */ 381 | } 382 | 383 | // From ghost@4.19.0/core/frontend/helpers/img_url.js: 384 | // “only make paths relative if we didn't get a request for an absolute url”. 385 | const maybeEnsureRelativePath = !absoluteUrlRequested ? ensureRelativePath : _.identity; 386 | 387 | // From Meteor v2.5.0 tools/cordova/builder.js: 388 | const boilerplate = new Boilerplate(CORDOVA_ARCH, manifest, { 389 | urlMapper: identity, 390 | /* … */ 391 | }); 392 | ``` 393 | 394 | Precedents include: 395 | * [lodash][]: [lodash.identity][] is individually downloaded from NPM 396 | about [700,000 times weekly][lodash.identity] 397 | * [stdlib][]: `import identity from '@stdlib/utils-identity-function';` 398 | * [fp-ts][]: `import { identity } from 'fp-ts/function';` 399 | * [Ramda][]: `import { identity } from 'ramda/src/identity';` 400 | 401 | [lodash.identity]: https://www.npmjs.com/package/lodash.identity 402 | 403 | ## Function.noop 404 | 405 | The `Function.noop` static method always returns undefined. 406 | `Function.noop` is equivalent to `() => {}`. 407 | It is also equivalent to `constant()`. 408 | 409 | This function is already available and frequently used from both [jQuery][] and Lodash, generally to fill a required callback argument or to disable a callback property. 410 | 411 | ```js 412 | const { noop } = Function; 413 | [ 0, 1 ].map(noop) 414 | // [ undefined, undefined ]. 415 | ``` 416 | 417 | The following real-world examples originally used 418 | jQuery’s [`$.noop`][] or [lodash.noop][]. 419 | 420 | ```js 421 | // From Wordpress v5.1.11: 422 | { /* … */ 423 | defaultExpandedArguments: { 424 | duration: 'fast', 425 | completeCallback: noop } 426 | /* … */ } 427 | 428 | // From three@0.133.1/test/benchmark/benchmark.js: 429 | SuiteUI.prototype.run = function() { 430 | this.runButton.click = noop; 431 | this.runButton.innerText = "Running..." 432 | this.suite.run({ async: true }); 433 | } 434 | 435 | // From typeahead.js@0.11.1/src/typeahead/dataset.js: 436 | this.cancel = function cancel() { 437 | canceled = true; 438 | that.cancel = noop; 439 | that.async && 440 | that.trigger('asyncCanceled', query); 441 | }; 442 | 443 | // From typeahead.js@0.11.1/src/bloodhound/bloodhound.js: 444 | // “if max size is less than 0, provide a noop cache”. 445 | sync = sync || noop; 446 | async = async || noop; 447 | sync(this.remote ? local.slice() : local); 448 | 449 | // From typeahead.js@0.11.1/src/bloodhound/lru_cache.js: 450 | // “if max size is less than 0, provide a noop cache”. 451 | if (this.maxSize <= 0) { 452 | this.set = this.get = $.noop; 453 | } 454 | 455 | // From verdaccio@5.1.6/packages/middleware/src/middleware.ts: 456 | errorReportingMiddleware(req, res, noop); 457 | 458 | // From Odoo v15.0 addons/bus/static/src/js/services/bus_service.js: 459 | Promise.resolve(this._audio.play()).catch(noop); 460 | 461 | // From ClickHouse v21.10.2.15-stable website/js/docsearch.js: 462 | if (this.$hint.length === 0) { 463 | this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = noop; 464 | } 465 | ``` 466 | 467 | Precedents include: 468 | * [jQuery][]: [`$.noop`][] 469 | * [lodash][]: [lodash.noop][] is individually downloaded from NPM 470 | about [415,600 times weekly][lodash.noop] 471 | 472 | [`$.noop`]: https://www.npmjs.com/package/lodash.noop 473 | [lodash.noop]: https://www.npmjs.com/package/lodash.noop 474 | 475 | ## Function.prototype.once 476 | The `Function.prototype.once` method creates a new function 477 | that calls the original function at most once, 478 | no matter how much the new function is called. 479 | ```js 480 | fn.once(); 481 | 482 | const fn = console.log.once(); 483 | fn(5); // Prints 5. 484 | fn(5); // Does not print anything. 485 | fn(5); // Does not print anything. 486 | 487 | const initialize = createApplication.once(); 488 | initialize(); 489 | initialize(); 490 | // createApplication is invoked only once. 491 | ``` 492 | 493 | The following real-world example originally used [lodash.once][]. 494 | 495 | ```js 496 | // From Meteor v2.2.1: 497 | // “Are we running Meteor from a git checkout?” 498 | export const inCheckout = (function () { 499 | try { /* … */ } catch (e) { console.log(e); } 500 | return false; 501 | }).once(); 502 | 503 | // From cypress@8.6.0: 504 | cy.on('command:retry', _.after(2, (() => { 505 | button.remove() /* … */ 506 | }).once())) 507 | 508 | // From Jitsi Meet v6482: 509 | this._hangup = (() => { 510 | sendAnalytics(createToolbarEvent('hangup')); 511 | /* … */ 512 | }).once() 513 | ``` 514 | 515 | Precedents include: 516 | * [lodash][]: [lodash.once][] is individually downloaded from NPM 517 | about [8,900,000 times weekly][lodash.once] 518 | * [jQuery][]: [`.one`][] 519 | * Node: [`eventEmitter.once`][] 520 | 521 | [lodash.once]: https://www.npmjs.com/package/lodash.once 522 | [`.one`]: https://api.jquery.com/one/ 523 | [`eventEmitter.once`]: https://nodejs.org/api/events.html#handling-events-only-once 524 | 525 | ## Function.prototype.debounce 526 | The `Function.prototype.debounce` method creates a new function 527 | that calls the original function at most once, 528 | no matter how much the new function is called. 529 | ```js 530 | fn.debounce(numOfMilliseconds); 531 | ``` 532 | 533 | Numerous graphical applications use `debounce`. 534 | In this example, logging happens on keyup events from `inputEl`, 535 | but only after the user has stopped typing for at least 250 ms: 536 | ```js 537 | inputEl.addEventListener('keyup', 538 | console.log.debounce(250)); 539 | ``` 540 | 541 | This method may come with options that could be bikeshedded in Stage 1. 542 | 543 | Precedents include: 544 | * [lodash][]: [lodash.debounce][] is individually downloaded from NPM 545 | about [11,400,000 times weekly][lodash.debounce] 546 | 547 | [lodash.debounce]: https://www.npmjs.com/package/lodash.debounce 548 | 549 | ## Function.prototype.throttle 550 | The `Function.prototype.throttle` method creates a new function that, when called, 551 | calls the original function—but only at most once within a given length of time. 552 | ```js 553 | fn.throttle(numOfMilliseconds); 554 | ``` 555 | 556 | Numerous graphical applications use `throttle`. 557 | In this example, logging happens on window scroll, but no more than once every 250 ms: 558 | ```js 559 | inputEl.addEventListener('keyup', 560 | console.log.throttle(250)); 561 | ``` 562 | 563 | This method may come with options that could be bikeshedded in Stage 1. 564 | 565 | Precedents include: 566 | * [lodash][]: [lodash.throttle][] is individually downloaded from NPM 567 | about [3,700,000 times weekly][lodash.throttle] 568 | 569 | [lodash.throttle]: https://www.npmjs.com/package/lodash.debounce 570 | 571 | ## Function.prototype.aside 572 | The `Function.prototype.aside` method creates a new unary function 573 | that applies some callback to its argument before returning the original argument. 574 | 575 | ```js 576 | fn.aside(); 577 | 578 | const { aside } = Function; 579 | 580 | console.log.aside(5); // Prints 5 before returning 5. 581 | 582 | arr.map(console.log.aside).map(f); 583 | // Prints each item from `arr` before passing them to `f`. 584 | 585 | const data = await Promise.resolve('intro.txt') 586 | .then(Deno.open) 587 | .then(Deno.readAll) 588 | .then(console.log.aside()) 589 | .then(data => new TextDecoder('utf-8').decode(data)); 590 | ``` 591 | 592 | The following real-world example originally used [lodash][].aside and lodash/fp’s pipe. 593 | 594 | ```js 595 | // From IBM/report-toolkit v0.6.1 packages/common/src/config.js: 596 | export function filterEnabledRules(config) { 597 | return pipe( 598 | config, 599 | _.getOr({}, 'rules'), 600 | _.toPairs, 601 | _.reduce( 602 | (enabledRules, [ruleName, ruleConfig]) => 603 | (_.isObject(ruleConfig) && _.get('enabled', ruleConfig)) || 604 | (_.isBoolean(ruleConfig) && ruleConfig) 605 | ? [ruleName, ...enabledRules] 606 | : enabledRules, 607 | [] 608 | ), 609 | (ruleIds => { 610 | debug('found %d enabled rule(s)', ruleIds.length); 611 | }).aside(); 612 | } 613 | ``` 614 | 615 | Precedents include: 616 | * [lodash][]: `_.tap` 617 | * [Ramda][]: `import { tap } from 'ramda/src/tap';` 618 | 619 | ## Function.prototype.unThis 620 | 621 | The `Function.prototype.unThis` method creates a new function 622 | that calls the original function, supplying its first argument 623 | as the original function’s `this` receiver, 624 | and supplying the rest of its arguments as the original function’s ordinary arguments. 625 | 626 | This is useful for converting `this`-based functions 627 | into non-`this`-based functions. 628 | 629 | ```js 630 | fn.unThis(); 631 | 632 | const $slice = Array.prototype.slice.unThis(); 633 | $slice([ 0, 1, 2 ], 1); // [ 1, 2 ]. 634 | ``` 635 | 636 | This is not a substitute for a [bind-this syntax][], 637 | which allows developers to change the receiver of functions 638 | without creating a wrapper function. 639 | 640 | [bind-this syntax]: https://github.com/js-choi/proposal-bind-this 641 | 642 | `fn.unThis()` is equivalent to\ 643 | `Function.prototype.call.bind(fn)` and to\ 644 | `Function.prototype.bind.bind(Function.prototype.call)(fn)`. 645 | 646 | Therefore, `fn.unThis()(thisArg, ...restArgs)` is equivalent 647 | to `fn.call(thisArg, ...restArgs)`. 648 | 649 | The following real-world example originally used [call-bind][] 650 | or a manually created similar function. 651 | 652 | ```js 653 | // From chrome-devtools-frontend@1.0.934332 654 | // node_modules/array-includes/test/implementation.js. 655 | runTests(implementation.unThis(), t); 656 | 657 | // From string.prototype.trimstart@1.0.4/index.js: 658 | var bound = getPolyfill().unThis(); 659 | 660 | // From andreasgal/dom.js (84b7ab6) src/snapshot.js. 661 | const /* … */ 662 | join = A.join || Array.prototype.join.unThis(), 663 | map = A.map || Array.prototype.map.unThis(), 664 | push = A.push || Array.prototype.push.unThis(), 665 | /* … */; 666 | ``` 667 | 668 | Precedents include: 669 | * [call-bind][]: `callBind` 670 | 671 | [lodash]: https://lodash.com/docs/4.17.15 672 | [stdlib]: https://github.com/stdlib-js/stdlib 673 | [RxJS]: https://rxjs.dev 674 | [fp-ts]: https://gcanti.github.io/fp-ts/ 675 | [Ramda]: https://ramdajs.com/ 676 | [jQuery]: https://jquery.com/ 677 | [call-bind]: https://www.npmjs.com/package/call-bind 678 | 679 | [pipe history]: https://github.com/tc39/proposal-pipeline-operator/blob/main/HISTORY.md 680 | 681 |
682 | --------------------------------------------------------------------------------